2023-08-09 09:57:34 +00:00
import cmath
import numpy as np
2023-08-08 15:09:28 +00:00
import logging
2023-07-31 11:20:14 +00:00
from PyQt6 . QtCore import pyqtSignal
2023-08-07 12:34:41 +00:00
from PyQt6 . QtSerialPort import QSerialPort
2023-03-23 15:08:59 +00:00
from nqrduck . module . module_model import ModuleModel
2023-08-08 15:09:28 +00:00
logger = logging . getLogger ( __name__ )
2023-08-09 09:57:34 +00:00
class S11Data :
# Conversion factors - the data is generally sent and received in mV
# These values are used to convert the data to dB and degrees
CENTER_POINT = 900 # mV
MAGNITUDE_SLOPE = 30 # dB/mV
PHASE_SLOPE = 10 # deg/mV
def __init__ ( self , data_points : list ) - > None :
self . frequency = np . array ( [ data_point [ 0 ] for data_point in data_points ] )
self . return_loss_mv = np . array ( [ data_point [ 1 ] for data_point in data_points ] )
self . phase_mv = np . array ( [ data_point [ 2 ] for data_point in data_points ] )
@property
def millivolts ( self ) :
return self . frequency , self . return_loss_mv , self . phase_mv
@property
def return_loss_db ( self ) :
return ( self . return_loss_mv - self . CENTER_POINT ) / self . MAGNITUDE_SLOPE
@property
def phase_deg ( self ) :
return ( self . phase_mv - self . CENTER_POINT ) / self . PHASE_SLOPE
@property
def phase_rad ( self ) :
return self . phase_deg * cmath . pi / 180
@property
def gamma ( self ) :
""" Complex reflection coefficient """
2023-08-10 07:07:51 +00:00
if len ( self . return_loss_db ) != len ( self . phase_rad ) :
raise ValueError ( " return_loss_db and phase_rad must be the same length " )
return [ cmath . rect ( 10 * * ( - loss_db / 20 ) , phase_rad ) for loss_db , phase_rad in zip ( self . return_loss_db , self . phase_rad ) ]
2023-08-09 14:32:13 +00:00
def to_json ( self ) :
return {
" frequency " : self . frequency . tolist ( ) ,
" return_loss_mv " : self . return_loss_mv . tolist ( ) ,
" phase_mv " : self . phase_mv . tolist ( )
}
@classmethod
def from_json ( cls , json ) :
f = json [ " frequency " ]
rl = json [ " return_loss_mv " ]
p = json [ " phase_mv " ]
data = [ ( f [ i ] , rl [ i ] , p [ i ] ) for i in range ( len ( f ) ) ]
return cls ( data )
2023-08-09 09:57:34 +00:00
2023-07-31 06:50:34 +00:00
class AutoTMModel ( ModuleModel ) :
2023-08-09 09:57:34 +00:00
2023-07-31 11:20:14 +00:00
available_devices_changed = pyqtSignal ( list )
2023-08-07 12:34:41 +00:00
serial_changed = pyqtSignal ( QSerialPort )
data_points_changed = pyqtSignal ( list )
2023-08-08 15:09:28 +00:00
2023-08-09 09:57:34 +00:00
short_calibration_finished = pyqtSignal ( S11Data )
open_calibration_finished = pyqtSignal ( S11Data )
load_calibration_finished = pyqtSignal ( S11Data )
measurement_finished = pyqtSignal ( S11Data )
2023-08-07 12:34:41 +00:00
def __init__ ( self , module ) - > None :
super ( ) . __init__ ( module )
self . data_points = [ ]
2023-08-08 15:09:28 +00:00
self . active_calibration = None
2023-08-10 07:07:51 +00:00
self . calibration = None
2023-07-31 11:20:14 +00:00
@property
def available_devices ( self ) :
return self . _available_devices
2023-08-08 15:09:28 +00:00
2023-07-31 11:20:14 +00:00
@available_devices.setter
def available_devices ( self , value ) :
self . _available_devices = value
self . available_devices_changed . emit ( value )
2023-07-31 13:24:46 +00:00
@property
def serial ( self ) :
2023-08-09 09:57:34 +00:00
""" The serial property is used to store the current serial connection.
"""
2023-07-31 13:24:46 +00:00
return self . _serial
2023-08-08 15:09:28 +00:00
2023-07-31 13:24:46 +00:00
@serial.setter
def serial ( self , value ) :
self . _serial = value
self . serial_changed . emit ( value )
2023-08-07 12:34:41 +00:00
2023-08-08 15:09:28 +00:00
def add_data_point ( self , frequency : float , return_loss : float , phase : float ) - > None :
2023-08-09 09:57:34 +00:00
""" Add a data point to the model. These data points are our intermediate data points read in via the serial connection.
They will be saved in the according properties later on .
"""
2023-08-08 15:09:28 +00:00
self . data_points . append ( ( frequency , return_loss , phase ) )
2023-08-07 12:34:41 +00:00
self . data_points_changed . emit ( self . data_points )
def clear_data_points ( self ) - > None :
2023-08-08 15:09:28 +00:00
""" Clear all data points from the model. """
2023-08-07 12:34:41 +00:00
self . data_points . clear ( )
self . data_points_changed . emit ( self . data_points )
2023-08-08 15:09:28 +00:00
2023-08-09 09:57:34 +00:00
@property
def measurement ( self ) :
""" The measurement property is used to store the current measurement.
This is the measurement that is shown in the main S11 plot """
return self . _measurement
@measurement.setter
def measurement ( self , value ) :
""" The measurement value is a tuple of three lists: frequency, return loss and phase. """
2023-08-09 14:32:13 +00:00
self . _measurement = value
self . measurement_finished . emit ( value )
2023-08-09 09:57:34 +00:00
# Calibration properties
2023-08-08 15:09:28 +00:00
@property
def active_calibration ( self ) :
return self . _active_calibration
@active_calibration.setter
def active_calibration ( self , value ) :
self . _active_calibration = value
@property
def short_calibration ( self ) :
return self . _short_calibration
@short_calibration.setter
def short_calibration ( self , value ) :
logger . debug ( " Setting short calibration " )
2023-08-09 14:32:13 +00:00
self . _short_calibration = value
self . short_calibration_finished . emit ( value )
2023-08-08 15:09:28 +00:00
def init_short_calibration ( self ) :
""" This method is called when a frequency sweep has been started for a short calibration in this way the module knows that the next data points are for a short calibration. """
self . active_calibration = " short "
self . clear_data_points ( )
@property
def open_calibration ( self ) :
return self . _open_calibration
@open_calibration.setter
def open_calibration ( self , value ) :
logger . debug ( " Setting open calibration " )
2023-08-09 14:32:13 +00:00
self . _open_calibration = value
self . open_calibration_finished . emit ( value )
2023-08-08 15:09:28 +00:00
def init_open_calibration ( self ) :
""" This method is called when a frequency sweep has been started for an open calibration in this way the module knows that the next data points are for an open calibration. """
self . active_calibration = " open "
self . clear_data_points ( )
@property
def load_calibration ( self ) :
return self . _load_calibration
@load_calibration.setter
def load_calibration ( self , value ) :
logger . debug ( " Setting load calibration " )
2023-08-09 14:32:13 +00:00
self . _load_calibration = value
self . load_calibration_finished . emit ( value )
2023-08-08 15:09:28 +00:00
def init_load_calibration ( self ) :
""" This method is called when a frequency sweep has been started for a load calibration in this way the module knows that the next data points are for a load calibration. """
self . active_calibration = " load "
self . clear_data_points ( )
@property
def calibration ( self ) :
return self . _calibration
@calibration.setter
def calibration ( self , value ) :
logger . debug ( " Setting calibration " )
self . _calibration = value
2023-08-09 09:57:34 +00:00