"""
Created on Mon Dec 13 10:40:31 2021
@author: Martin.Mihaylov
"""
import numpy as np
from .BaseInstrument import BaseInstrument
[docs]
class MS4647B(BaseInstrument):
"""
This class uses BaseInstrument to connect to an Anritsu MS4647B VNA.
"""
def __init__(self, resource_str: str, visa_library: str = "@py", **kwargs):
# Do not modify read_termination; pass exactly as in old script
super().__init__(resource_str, visa_library=visa_library, **kwargs)
print(self.get_idn())
# =============================================================================
# Validate functions
# =============================================================================
def _validate_channel_number(self, channel_num: int) -> None:
"""
Validate channel number.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
if not isinstance(channel_num, int):
raise ValueError("Channel number must be an integer.")
# =============================================================================
# Return to local
# =============================================================================
[docs]
def rtl(self) -> None:
"""
Send all devices to local operation. No query.
"""
self.write("RTL")
# =============================================================================
# Ask
# =============================================================================
[docs]
def get_sub_system(self) -> str:
"""
The :SENSe:HOLD subsystem command sets the hold function for all
channels on a per-instrument basis.
"""
return self.query(":SENSe:HOLD:FUNCtion?")
[docs]
def get_sweep_count(self, channel_num: int) -> float:
"""
Query only. Outputs the averaging sweep count for the indicated channel.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
self._validate_channel_number(channel_num)
return float(self.query(":SENS" + str(channel_num) + ":AVER:SWE?"))
[docs]
def get_test_set(self, channel_num: int) -> str:
"""
Query State of TS3739.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
self._validate_channel_number(channel_num)
return self.query(":SENS" + str(channel_num) + ":TS3739:STATe?")
[docs]
def get_sys_errors(self) -> str:
"""
Query only. Outputs the number of errors in the error queue.
"""
return self.query(":SYST:ERR:COUN?")
[docs]
def get_stat_operation(self) -> int:
"""
Query only. Outputs the value of the operation status condition reg.
Range: 0 to 32767
Default Value: 0
"""
return int(float(self.query(":STAT:OPER:COND?")))
[docs]
def get_stat_operation_register(self) -> int:
"""
Sets the value of the operation status enable register.
Outputs the value of the operation status enable register.
"""
return int(float(self.query(":STATus:OPERation:ENABle?")))
[docs]
def get_freq_span(self, channel_num: int) -> float:
"""
Optional query. Span is automatically calculated as Stop Frequency minus
Start Frequency. The query returns the resulting span in Hertz.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
self._validate_channel_number(channel_num)
return float(self.query(":SENSe" + str(channel_num) + ":FREQuency:SPAN?"))
[docs]
def get_center_freq(self, channel_num: int) -> float:
"""
Optional query. Center frequency is automatically calculated using Stop Frequency
and Start Frequency as: Fc = ((Fstop - Fstart)/2) + Fstart
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
self._validate_channel_number(channel_num)
return float(self.query(":SENSe" + str(channel_num) + ":FREQuency:CENTer?"))
[docs]
def get_cw_freq(self, channel_num: int) -> float:
"""
Sets the CW frequency of the indicated channel. Outputs the CW
frequency of the indicated channel.
The output parameter is in Hertz.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
self._validate_channel_number(channel_num)
return float(self.query(":SENS" + str(channel_num) + ":FREQ:CW?"))
[docs]
def get_data_freq(self, channel_num: int) -> str:
"""
Outputs the frequency list for the indicated channel.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
self._validate_channel_number(channel_num)
return self.query(":SENSe" + str(channel_num) + ":FREQuency:DATA?")
[docs]
def get_sweep_channel_status(self) -> str:
"""
The query outputs the On/Off state of the option to sweep only the active
channel
"""
return self.query(":DISP:ACT:CHAN:SWE:STAT?")
[docs]
def get_assignet_data_port(self, value: int) -> str:
"""
Outputs the data port pair assigned to use when creating an sNp data
file on the indicated channel.
Parameters
----------
value : int/float
the N(ports number) for the .sNp data output.
"""
_value = str(value)
if _value in ["1", "2", "3", "4"]:
return self.query("FORMat:S" + str(_value) + "P:PORT?")
else:
raise ValueError("Unknown input! See function description for more info.")
[docs]
def get_rf_state(self) -> str:
"""
Outputs the RF on/off state in Hold.
"""
return self.query(":SYST:HOLD:RF?")
[docs]
def get_set_average_state(self, channel_num: int) -> str:
"""
Outputs the averaging function on/off status on the indicated channel.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
self._validate_channel_number(channel_num)
return self.query(":SENS" + str(channel_num) + ":AVER?")
[docs]
def get_average_function_type(self, channel_num: int) -> str:
"""
Outputs the averaging function type of point-by-point or sweep-by-sweep.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
self._validate_channel_number(channel_num)
return self.query(":SENS" + str(channel_num) + ":AVER:TYP?")
[docs]
def get_average_count(self, channel_num: int) -> float:
"""
Outputs the averaging count for the indicated channel.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
self._validate_channel_number(channel_num)
return float(self.query(":SENS" + str(channel_num) + ":AVER:COUN?"))
[docs]
def get_transfer_data(self, name: str, port_num: int) -> str:
"""
The query outputs the disk file data to the GPIB. The file must exist.
Hard coded path on the VNA = 'C:/tmp/'
Parameters
----------
name : str
File Name
port_num : int
the N(ports number) for the .sNp data output.
"""
path = "C:/tmp/"
path = str(path) + str(name) + "_.s" + str(port_num) + "p"
return self.query(":MMEM:TRAN? " + '"' + path + '"')
[docs]
def get_transfer_data_csv(self, name: str) -> str:
"""
The query outputs the disk file data to the GPIB. The file must exist.
Hard coded path on the VNA = 'C:/tmp/'
Parameters
----------
name : str
File Name
"""
path = "C:/tmp/"
path = str(path) + str(name) + "_.csv"
return self.query(":MMEM:TRAN? " + '"' + path + '"')
[docs]
def get_resolution_bw(self, channel_num: int) -> float:
"""
The command sets the IF bandwidth for the indicated channel. The query outputs the IF
bandwidth for the indicated channel.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
self._validate_channel_number(channel_num)
return float(self.query(":SENS" + str(channel_num) + ":BAND?"))
[docs]
def get_power_on_port(self, segment: int, channel_num: int) -> float:
"""
Outputs the power level of the indicated port on the indicated channel.
Parameters
----------
segment : int
Selected Source. Can be from 1-16
channel_num : int
Channel Number 1,2,3...
"""
segment_list = np.arange(1, 17, 1)
channel_num_list = np.arange(1, 5, 1)
if segment in segment_list and channel_num in channel_num_list:
return float(self.query(":SOUR" + str(segment) + ":POW:PORT" + str(channel_num) + "?"))
else:
raise ValueError("Unknown input! See function description for more info.")
[docs]
def get_smoothing_state(self, channel_num: int) -> float:
"""
Query outputs the smoothing on/off status for the indicated channel and active trace.
1 = ON
2 = OFF
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
self._validate_channel_number(channel_num)
return float(self.query(":CALC" + str(channel_num) + ":SMO?"))
[docs]
def get_display_trace(self) -> str:
"""
Query only. Outputs the Active Channel number.
"""
return self.query(":DISPlay:WINDow:ACTivate?")
[docs]
def get_display_count(self) -> float:
"""
Query the number of displayed channels.
"""
return float(self.query(":DISP:COUN?"))
[docs]
def get_display_title(self) -> str:
"""
Outputs the user title for the channel indicated.
"""
return self.query(":DISP:WIND1:TITL?")
[docs]
def get_select_parameter(self) -> str:
"""
The query outputs only the selected parameter.
"""
return self.query(":CALC1:PAR1:DEF?")
[docs]
def get_sweep_delay(self) -> float:
"""
Outputs the sweep delay time of the indicated channel.
"""
return float(self.query(":SENS1:SWE:DEL?"))
[docs]
def get_sweep_time(self) -> float:
"""
Outputs the Sweep Time of the indicated channel.
"""
return float(self.query(":SENS1:SWE:TIM?"))
# =============================================================================
# Set
# =============================================================================
[docs]
def set_clear_average(self, channel_num: int) -> None:
"""
Clears and restarts the averaging sweep count of the indicated channel.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
self._validate_channel_number(channel_num)
self.write(":SENS" + str(channel_num) + ":AVER:CLE")
[docs]
def set_sub_system_hold(self) -> None:
"""
Sets the hold function for all channels on a per-instrument basis.
The sweep is stopped.
"""
self.write(":SENS:HOLD:FUNC HOLD")
[docs]
def set_sub_system_sing(self) -> None:
"""
The sweep restarts and sweeps until the end of the
sweep, at which point it sets the end of sweep status bit and
stops.
"""
self.write(":SENS:HOLD:FUNC SING")
[docs]
def set_sub_system_cont(self) -> None:
"""
The sweep is sweeping continuously.
"""
self.write(":SENS:HOLD:FUNC CONT")
[docs]
def set_display_scale(self) -> None:
"""
Auto scales all traces on all channels.
"""
self.write(":DISPlay:Y:AUTO")
[docs]
def set_ts3739(self, channel_num: int, state: str | int) -> None:
"""
The :SENSe{1-16}:TS3739 subsystem commands are used to configure and
control the VectorStar ME7838x Broadband/Millimeter-Wave 3738A Test Set.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
state : str/int
ON/OFF or 1/0.
"""
self._validate_channel_number(channel_num)
parsed_state = self._parse_state(state)
self.write(":SENS" + str(channel_num) + ":TS3739 " + parsed_state)
[docs]
def set_clear_error(self) -> None:
"""
Clears the contents of the error queue.
"""
self.write(":SYST:ERR:CLE")
[docs]
def set_display_color_reset(self) -> None:
"""
Resets all colors and inverted colors to their normal default values.
"""
self.write(":DISP:COL:RES")
[docs]
def set_stat_operation_register(self, value: int) -> None:
"""
Sets the value of the operation status enable register.
Outputs the value of the operation status enable register.
Parameters
----------
value : int
The input parameter is a unitless number.
Range: 0 to 65535
"""
if isinstance(value, int) and 0 <= value <= 65535:
self.write(":STAT:OPER:ENAB " + str(value))
else:
raise ValueError("Value must be an integer between 0 and 65535.")
[docs]
def set_start_freq(self, channel_num: int, value: int | str) -> None:
"""
Sets the start value of the sweep range of the indicated channel.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
value : int/str in form - 10E+9
The input parameter is in Hertz, Meters, or Seconds.
"""
self._validate_channel_number(channel_num)
self.write(":SENSe" + str(channel_num) + ":FREQuency:STARt " + str(value))
[docs]
def set_stop_freq(self, channel_num: int, value: int | str) -> None:
"""
Sets the stop value of the sweep range of the indicated channel.
The input parameter is in Hertz, Meters, or Seconds.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
value : int/str in form - 10E+9
"""
self._validate_channel_number(channel_num)
self.write(":SENSe" + str(channel_num) + ":FREQuency:STOP " + str(value))
[docs]
def set_center_freq(self, channel_num: int, value: int | str) -> None:
"""
Sets the center value of the sweep range of the indicated channel.
Outputs the center value of the sweep range of the indicated channel
Parameters
----------
channel_num : int
Channel Number 1,2,3...
value : int/str in form - 10E+9
"""
self._validate_channel_number(channel_num)
self.write(":SENS" + str(channel_num) + ":FREQ:CENT " + str(value))
[docs]
def set_cw_freq(self, channel_num: int, value: int | str) -> None:
"""
Sets the CW frequency of the indicated channel.
Outputs the CW frequency of the indicated channel.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
value : int/str in form - 10E+9
"""
self._validate_channel_number(channel_num)
self.write(":SENS" + str(channel_num) + ":FREQ:CW " + str(value))
[docs]
def set_sweep_channel_status(self, state: str | int) -> None:
"""
The command turns On/Off the option to sweep only the active channel
Parameters
----------
state : str/int
ON/OFF or 1/0.
"""
parsed_state = self._parse_state(state)
self.write(":DISP:ACT:CHAN:SWE:STAT " + parsed_state)
[docs]
def set_assignet_data_port(self, channel_num: int, value1: int, value2: int) -> None:
"""
The command assigns the data port pair to use when creating an sNp
data file on the indicated channel. The use of Port 3 and/or Port 4
requires a 4-port VNA instrument.
PORT12 | PORT13 | PORT14 | PORT23 | PORT24 | PORT34
Parameters
----------
channel_num : int
Channel Number 1,2,3...
value1 : int
value2 : int
"""
_value1 = str(value1)
_value2 = str(value2)
if isinstance(channel_num, int):
if _value1 in ["1", "2", "3", "4"]:
self.write(
":CALC"
+ str(channel_num)
+ ":FORM:S"
+ str(_value1)
+ "P:PORT PORT"
+ str(_value1)
+ str(_value2)
)
else:
raise ValueError("Unknown input! See function description for more info.")
else:
raise ValueError("Unknown input! See function description for more info.")
[docs]
def set_rf_state(self, state: str | int) -> None:
"""
Sets the RF on/off state in Hold.
Parameters
----------
state : str/int
Sets the RF on/off state in Hold.
"""
parsed_state = self._parse_state(state)
self.write(":SYST:HOLD:RF " + parsed_state)
[docs]
def set_set_average_state(self, channel_num: int, state: str | int) -> None:
"""
Turns averaging on/off for the indicated channel
(Turns ON and Off the averaging for all channels).
Parameters
----------
channel_num : int
Channel Number 1,2,3...
state : int/str
ON/OFF or 1/0.
"""
self._validate_channel_number(channel_num)
parsed_state = self._parse_state(state)
self.write(":SENS" + str(channel_num) + ":AVER " + parsed_state)
[docs]
def set_average_function_type(self, channel_num: int, state: str) -> None:
"""
Sets the averaging function type to point-by-point or sweep-by-sweep.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
state : str
Sets the averaging function type to point-by-point or sweep-by-sweep.
POIN | SWE
Default Value: POIN
"""
self._validate_channel_number(channel_num)
state_parsed = self._check_scpi_param(state, ["POINt", "SWEep"])
self.write(":SENS" + str(channel_num) + ":AVER:TYP " + state_parsed)
[docs]
def set_average_count(self, channel_num: int, value: int) -> None:
"""
Sets the averaging count for the indicated channel. The channel must
be turned on.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
value : int
The input parameter is a unitless number. Range: 1 to 1024
Default Value: 1
"""
_value = str(value)
self._validate_channel_number(channel_num)
self.write(":SENS" + str(channel_num) + ":AVER:COUN " + str(_value))
[docs]
def set_resolution_bw(self, channel_num: int, value: int | float | str) -> None:
"""
Sets the IF bandwidth for the indicated channel.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
value : int/floa/str
IF Bandwidth in Hz
"""
value = str(value)
self._validate_channel_number(channel_num)
self.write(":SENS" + str(channel_num) + ":BAND " + str(value))
[docs]
def set_power_on_port(self, segment: int, channel_num: int, value: int | float | str) -> None:
"""
Sets the power level of the indicated port on the indicated channel.
Parameters
----------
segment : int
Selected Source. Can be from 1-16
channel_num : int
Channel Number 1,2,3...
value : int/floa/str
Power level
"""
segment_list = np.arange(1, 17, 1)
channel_num_list = np.arange(1, 5, 1)
if segment in segment_list and channel_num in channel_num_list:
self.write(":SOUR" + str(segment) + ":POW:PORT" + str(channel_num) + " " + str(value))
else:
raise ValueError("Unknown input! See function description for more info.")
[docs]
def set_smoothing_state(self, channel_num: int, state: str | int) -> None:
"""
Turns smoothing on/off for the indicated channel.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
state : str/int
can be int or str form the list ['ON','OFF',1,0]
"""
self._validate_channel_number(channel_num)
parsed_state = self._parse_state(state)
self.write(":CALC" + str(channel_num) + ":SMO " + parsed_state)
[docs]
def set_smoothing_ape_rture(self, channel_num: int, value: int) -> None:
"""
Parameters
----------
channel_num : int
Channel Number 1,2,3...
value : int
Percentage smoothing between 0 to 100
"""
self._validate_channel_number(channel_num)
self.write(":CALC" + str(channel_num) + "SMO:APER " + str(float(value)))
[docs]
def set_display_trace(self, channel_num: int) -> None:
"""
The command sets the active channel to the indicated number. When the VNA is set to
100,000 point mode, the number of channels is 1.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
self._validate_channel_number(channel_num)
self.write(":DISP:WIND" + str(channel_num) + ":ACT")
[docs]
def set_display_count(self, channel_num: int) -> None:
"""
Sets the number of displayed channels. When the VNA is in 25,000 point mode, the
number of channels can only be 1 (one), 2, 3, 4, 6, 8, 9, 10, 12, or 16 channels. If the
channel display is set to a non-listed number (5, 7, 11, 13, 14, 15), the instrument is set
to the next higher channel number. If a number of greater than 16 is entered, the
instrument is set to 16 channels. If the instrument is set to 100,000 points, any input
results in 1 (one) channel. Outputs the number of displayed channels.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
self._validate_channel_number(channel_num)
self.write(":DISP:COUN " + str(channel_num))
[docs]
def set_display_title(self, channel_name: str) -> None:
"""
Sets the user title for the channel indicated.
Parameters
----------
channel_num : int
Channel Number 1,2,3...
"""
if isinstance(channel_name, str):
self.write(":DISP:WIND1:TITL " + channel_name)
else:
raise ValueError("Unknown input! See function description for more info.")
[docs]
def set_select_parameter(self, s_param: str) -> None:
"""
Select an S-Parameter. 16 S-Parameters for 4 Ports config can be selected.
Parameters
----------
s_param : str
S-Parameter selected.
"""
s_param_list = [
"S11",
"S12",
"S13",
"S14",
"S21",
"S22",
"S23",
"S24",
"S31",
"S32",
"S33",
"S34",
"S41",
"S42",
"S43",
"S44",
]
if isinstance(s_param, str):
if s_param in s_param_list:
self.write(":CALC1:PAR1:DEF " + s_param)
else:
raise ValueError("Unknown input! See function description for more info.")
else:
raise ValueError("Unknown input! See function description for more info.")
[docs]
def set_sweep_delay(self, time: float) -> None:
"""
Parameters
----------
time : float
Sets the sweep delay time of the indicated channel.
"""
self.write(":SENS1:SWE:DEL " + str(time))
[docs]
def set_sweep_time(self, time: float) -> None:
"""
Parameters
----------
time : float
Sets the Sweep Time of the indicated channel.
"""
self.write(":SENS1:SWE:TIM " + str(time))
# =============================================================================
# Save
# =============================================================================
[docs]
def save_data(self, name: str, port_num: int) -> None:
"""
Parameters
----------
name : str
File Name
port_num : TYPE
The N(ports number) for the .sNp data output.
Description: Stores a data file of the type specified by the filename
extension.No query.
Hard coded path on the VNA = 'C:/tmp/'
"""
path = "C:/tmp/"
path = str(path) + str(name) + "_.s" + str(port_num) + "p"
self.write(":MMEM:STOR " + '"' + path + '"')
[docs]
def save_data_csv(self, name: str) -> None:
"""
Parameters
----------
name : str
File Name
Description: Stores a data file of the type specified by the filename
extension.No query.
Hard coded path on the VNA = 'C:/tmp/'
"""
path = "C:/tmp/"
path = str(path) + str(name) + "_.csv"
self.write(":MMEM:STOR " + '"' + path + '"')
[docs]
def save_image(self, name: str) -> None:
"""
Parameters
----------
name : str
File Name
Description: Stores a data file of the type specified by the filename
extension.No query.
Hard coded path on the VNA = 'C:/tmp/'
"""
path = "C:/tmp/Image/"
path = str(path) + str(name) + "_.png"
self.write(":MMEMory:STORe:IMAGe " + '"' + path + '"')
[docs]
def delete_data(self, name: str, port_num: int) -> None:
"""
Parameters
----------
name : str
File Name
port_num : TYPE
The N(ports number) for the .sNp data output.
Delete a disk, file, or directory. Use caution with this command as there is no
recovery operation in case of a user mistake or error. No query
Hard coded path on the VNA = 'C:/tmp/'
"""
path = "C:/tmp/"
path = str(path) + str(name) + "_.s" + str(port_num) + "p"
self.write(":MMEMory:DEL " + '"' + path + '"')
[docs]
def delete_data_csv(self, name: str) -> None:
"""
Parameters
----------
name : str
File Name
Delete a disk, file, or directory. Use caution with this command as there is no
recovery operation in case of a user mistake or error. No query
Hard coded path on the VNA = 'C:/tmp/'
"""
path = "C:/tmp/"
path = str(path) + str(name) + "_.csv"
self.write(":MMEMory:DEL " + '"' + path + '"')
[docs]
def save_transfer_data(self, file: str, path: str, name: str, port_num: int) -> None:
"""
Parameters
----------
file : str
File data extracted from function ask_TransferData
path : str
Where on the PC to save the data
name : str
Name of the File
port_num : int/str
The N(ports number) for the .sNp data output.
Write a text File with the transferred data
"""
readinglines = file.splitlines()
with open(str(path) + "/" + str(name) + ".s" + str(port_num) + "p", "w") as f:
for line in readinglines:
f.write(line)
f.write("\n")
[docs]
def save_transfer_data_csv(self, file: str, path: str, name: str) -> None:
"""
Parameters
----------
file : str
File data extracted from function ask_TransferData
path : str
Where on the PC to save the data
name : str
Name of the File
Write a text File with the transferred data
"""
readinglines = file.splitlines()
with open(str(path) + "/" + str(name) + ".csv", "w") as f:
for line in readinglines:
f.write(line)
f.write("\n")