#!/usr/bin/env python
"""
Provides support for the Holzworth HS9000
"""
# IMPORTS #####################################################################
from instruments.units import ureg as u
from instruments.abstract_instruments.signal_generator import SignalGenerator, SGChannel
from instruments.util_fns import (
ProxyList,
split_unit_str,
bounded_unitful_property,
bool_property,
)
# CLASSES #####################################################################
[docs]
class HS9000(SignalGenerator):
"""
Communicates with a `Holzworth HS-9000 series`_ multi-channel frequency
synthesizer.
.. _Holzworth HS-9000 series: http://www.holzworth.com/synthesizers-multi.htm
"""
# INNER CLASSES #
[docs]
class Channel(SGChannel):
"""
Class representing a physical channel on the Holzworth HS9000
.. warning:: This class should NOT be manually created by the user. It
is designed to be initialized by the `HS9000` class.
"""
def __init__(self, hs, idx_chan):
self._hs = hs
self._idx = idx_chan
# We unpacked the channel index from the string of the form "CH1",
# in order to make the API more Pythonic, but now we need to put
# it back.
# Some channel names, like "REF", are special and are preserved
# as strs.
self._ch_name = (
idx_chan if isinstance(idx_chan, str) else f"CH{idx_chan + 1}"
)
# PRIVATE METHODS #
[docs]
def sendcmd(self, cmd):
"""
Function used to send a command to the instrument while wrapping
the command with the neccessary identifier for the channel.
:param str cmd: Command that will be sent to the instrument after
being prefixed with the channel identifier
"""
self._hs.sendcmd(f":{self._ch_name}:{cmd}")
[docs]
def query(self, cmd):
"""
Function used to send a command to the instrument while wrapping
the command with the neccessary identifier for the channel.
:param str cmd: Command that will be sent to the instrument after
being prefixed with the channel identifier
:return: The result from the query
:rtype: `str`
"""
return self._hs.query(f":{self._ch_name}:{cmd}")
# STATE METHODS #
[docs]
def reset(self):
"""
Resets the setting of the specified channel
Example usage:
>>> import instruments as ik
>>> hs = ik.holzworth.HS9000.open_tcpip("192.168.0.2", 8080)
>>> hs.channel[0].reset()
"""
self.sendcmd("*RST")
[docs]
def recall_state(self):
"""
Recalls the state of the specified channel from memory.
Example usage:
>>> import instruments as ik
>>> hs = ik.holzworth.HS9000.open_tcpip("192.168.0.2", 8080)
>>> hs.channel[0].recall_state()
"""
self.sendcmd("*RCL")
[docs]
def save_state(self):
"""
Saves the current state of the specified channel.
Example usage:
>>> import instruments as ik
>>> hs = ik.holzworth.HS9000.open_tcpip("192.168.0.2", 8080)
>>> hs.channel[0].save_state()
"""
self.sendcmd("*SAV")
# PROPERTIES #
@property
def temperature(self):
"""
Gets the current temperature of the specified channel.
:units: As specified by the instrument.
:rtype: `~pint.Quantity`
"""
val, units = split_unit_str(self.query("TEMP?"))
units = f"deg{units}"
return u.Quantity(val, units)
frequency, frequency_min, frequency_max = bounded_unitful_property(
"FREQ",
units=u.GHz,
doc="""
Gets/sets the frequency of the specified channel. When setting,
values are bounded between what is returned by `frequency_min`
and `frequency_max`.
Example usage:
>>> import instruments as ik
>>> hs = ik.holzworth.HS9000.open_tcpip("192.168.0.2", 8080)
>>> print(hs.channel[0].frequency)
>>> print(hs.channel[0].frequency_min)
>>> print(hs.channel[0].frequency_max)
:type: `~pint.Quantity`
:units: As specified or assumed to be of units GHz
""",
)
power, power_min, power_max = bounded_unitful_property(
"PWR",
units=u.dBm,
doc="""
Gets/sets the output power of the specified channel. When setting,
values are bounded between what is returned by `power_min`
and `power_max`.
Example usage:
>>> import instruments as ik
>>> hs = ik.holzworth.HS9000.open_tcpip("192.168.0.2", 8080)
>>> print(hs.channel[0].power)
>>> print(hs.channel[0].power_min)
>>> print(hs.channel[0].power_max)
:type: `~pint.Quantity`
:units: `instruments.units.dBm`
""",
)
phase, phase_min, phase_max = bounded_unitful_property(
"PHASE",
units=u.degree,
doc="""
Gets/sets the output phase of the specified channel. When setting,
values are bounded between what is returned by `phase_min`
and `phase_max`.
Example usage:
>>> import instruments as ik
>>> hs = ik.holzworth.HS9000.open_tcpip("192.168.0.2", 8080)
>>> print(hs.channel[0].phase)
>>> print(hs.channel[0].phase_min)
>>> print(hs.channel[0].phase_max)
:type: `~pint.Quantity`
:units: As specified or assumed to be of units degrees
""",
)
output = bool_property(
"PWR:RF",
inst_true="ON",
inst_false="OFF",
set_fmt="{}:{}",
doc="""
Gets/sets the output status of the channel. Setting to `True` will
turn the channel's output stage on, while a value of `False` will
turn it off.
Example usage:
>>> import instruments as ik
>>> hs = ik.holzworth.HS9000.open_tcpip("192.168.0.2", 8080)
>>> print(hs.channel[0].output)
>>> hs.channel[0].output = True
:type: `bool`
""",
)
# PROXY LIST ##
def _channel_idxs(self):
"""
Internal function used to get the list of valid channel names
to be used by `HS9000.channel`
:return: A list of valid channel indicies
:rtype: `list` of `int` and `str`
"""
# The command :ATTACH? returns a string of the form ":CH1:CH2" to
# indicate what channels are attached to the internal USB bus.
# We convert what channel names we can to integers, and leave the
# rest as strings.
return [
(
int(ch_name.replace("CH", "")) - 1
if ch_name.startswith("CH")
else ch_name.strip()
)
for ch_name in self.query(":ATTACH?").split(":")
if ch_name
]
@property
def channel(self):
"""
Gets a specific channel on the HS9000. The desired channel is accessed
like one would access a list.
Example usage:
>>> import instruments as ik
>>> hs = ik.holzworth.HS9000.open_tcpip("192.168.0.2", 8080)
>>> print(hs.channel[0].frequency)
:return: A channel object for the HS9000
:rtype: `~HS9000.Channel`
"""
return ProxyList(self, self.Channel, self._channel_idxs())
# OTHER PROPERTIES #
@property
def name(self):
"""
Gets identification string of the HS9000
:return: The string as usually returned by ``*IDN?`` on SCPI instruments
:rtype: `str`
"""
# This is a weird one; the HS-9000 associates the :IDN? command
# with each individual channel, though we want it to be a synthesizer-
# wide property. To solve this, we assume that CH1 is always a channel
# and ask its name.
return self.channel[0].query("IDN?")
@property
def ready(self):
"""
Gets the ready status of the HS9000.
:return: If the instrument is ready for operation
:rtype: `bool`
"""
return "Ready" in self.query(":COMM:READY?")