blob: 40395cb9098cb2ada1f9d697b0ffc81f48afb33c [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2022 The Fuchsia Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Class for Telnet control of Mini-Circuits RCDAT series attenuators
This class provides a wrapper to the MC-RCDAT attenuator modules for purposes
of simplifying and abstracting control down to the basic necessities. It is
not the intention of the module to expose all functionality, but to allow
interchangeable HW to be used.
See http://www.minicircuits.com/softwaredownload/Prog_Manual-6-Programmable_Attenuator.pdf
"""
from antlion.controllers import attenuator
from antlion.controllers.attenuator_lib import _tnhelper
class AttenuatorInstrument(attenuator.AttenuatorInstrument):
"""A specific telnet-controlled implementation of AttenuatorInstrument for
Mini-Circuits RC-DAT attenuators.
With the exception of telnet-specific commands, all functionality is defined
by the AttenuatorInstrument class. Because telnet is a stateful protocol,
the functionality of AttenuatorInstrument is contingent upon a telnet
connection being established.
"""
def __init__(self, num_atten=0):
super(AttenuatorInstrument, self).__init__(num_atten)
self._tnhelper = _tnhelper._TNHelper(
tx_cmd_separator="\r\n", rx_cmd_separator="\r\n", prompt=""
)
self.address = None
def __del__(self):
if self.is_open():
self.close()
def open(self, host, port=23):
"""Opens a telnet connection to the desired AttenuatorInstrument and
queries basic information.
Args:
host: A valid hostname (IP address or DNS-resolvable name) to an
MC-DAT attenuator instrument.
port: An optional port number (defaults to telnet default 23)
"""
self._tnhelper.open(host, port)
self.address = host
if self.num_atten == 0:
self.num_atten = 1
config_str = self._tnhelper.cmd("MN?")
if config_str.startswith("MN="):
config_str = config_str[len("MN=") :]
self.properties = dict(
zip(["model", "max_freq", "max_atten"], config_str.split("-", 2))
)
self.max_atten = float(self.properties["max_atten"])
def is_open(self):
"""Returns True if the AttenuatorInstrument has an open connection."""
return bool(self._tnhelper.is_open())
def close(self):
"""Closes the telnet connection.
This should be called as part of any teardown procedure prior to the
attenuator instrument leaving scope.
"""
self._tnhelper.close()
def set_atten(self, idx, value, strict=True, retry=False):
"""This function sets the attenuation of an attenuator given its index
in the instrument.
Args:
idx: A zero-based index that identifies a particular attenuator in
an instrument. For instruments that only have one channel, this
is ignored by the device.
value: A floating point value for nominal attenuation to be set.
strict: if True, function raises an error when given out of
bounds attenuation values, if false, the function sets out of
bounds values to 0 or max_atten.
retry: if True, command will be retried if possible
Raises:
InvalidOperationError if the telnet connection is not open.
IndexError if the index is not valid for this instrument.
ValueError if the requested set value is greater than the maximum
attenuation value.
"""
if not self.is_open():
raise attenuator.InvalidOperationError("Connection not open!")
if idx >= self.num_atten:
raise IndexError("Attenuator index out of range!", self.num_atten, idx)
if value > self.max_atten and strict:
raise ValueError("Attenuator value out of range!", self.max_atten, value)
# The actual device uses one-based index for channel numbers.
adjusted_value = min(max(0, value), self.max_atten)
self._tnhelper.cmd(f"CHAN:{idx + 1}:SETATT:{adjusted_value}", retry=retry)
def get_atten(self, idx, retry=False):
"""Returns the current attenuation of the attenuator at the given index.
Args:
idx: The index of the attenuator.
retry: if True, command will be retried if possible
Raises:
InvalidOperationError if the telnet connection is not open.
Returns:
the current attenuation value as a float
"""
if not self.is_open():
raise attenuator.InvalidOperationError("Connection not open!")
if idx >= self.num_atten or idx < 0:
raise IndexError("Attenuator index out of range!", self.num_atten, idx)
if self.num_atten == 1:
atten_val_str = self._tnhelper.cmd(":ATT?", retry=retry)
else:
atten_val_str = self._tnhelper.cmd(f"CHAN:{idx + 1}:ATT?", retry=retry)
atten_val = float(atten_val_str)
return atten_val