Exemplo n.º 1
0
class InstrumentController(QObject):
    pointReady = pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent=parent)

        addrs = load_ast_if_exists('instr.ini',
                                   default={
                                       'Осциллограф': 'GPIB1::7::INSTR',
                                       'Анализатор': 'GPIB1::18::INSTR',
                                       'P LO': 'GPIB1::6::INSTR',
                                       'P RF': 'GPIB1::20::INSTR',
                                       'Источник': 'GPIB1::3::INSTR',
                                       'Мультиметр': 'GPIB1::22::INSTR',
                                   })

        self.requiredInstruments = {
            'Осциллограф': OscilloscopeFactory(addrs['Осциллограф']),
            'Анализатор': AnalyzerFactory(addrs['Анализатор']),
            'P LO': GeneratorFactory(addrs['P LO']),
            'P RF': GeneratorFactory(addrs['P RF']),
            'Источник': SourceFactory(addrs['Источник']),
            'Мультиметр': MultimeterFactory(addrs['Мультиметр']),
        }

        self.deviceParams = {
            'Демодулятор': {
                'F': 1,
            },
        }

        self.secondaryParams = load_ast_if_exists('params.ini',
                                                  default={
                                                      'Plo_min': -10.0,
                                                      'Plo_max': -10.0,
                                                      'Plo_delta': 1.0,
                                                      'Flo_min': 0.1,
                                                      'Flo_max': 3.0,
                                                      'Flo_delta': 0.1,
                                                      'is_Flo_x2': False,
                                                      'Prf': -10.0,
                                                      'Frf_min': 0.11,
                                                      'Frf_max': 3.1,
                                                      'Frf_delta': 0.1,
                                                      'Usrc': 5.0,
                                                      'OscAvg': True,
                                                      'loss': 0.82,
                                                      'scale_y': 0.7,
                                                      'timebase_coeff': 1.0,
                                                  })

        self._calibrated_pows_lo = load_ast_if_exists('cal_lo.ini', default={})
        self._calibrated_pows_rf = load_ast_if_exists('cal_rf.ini', default={})

        self._instruments = dict()
        self.found = False
        self.present = False
        self.hasResult = False
        self.only_main_states = False

        self.result = MeasureResult()

    def __str__(self):
        return f'{self._instruments}'

    def connect(self, addrs):
        print(f'searching for {addrs}')
        for k, v in addrs.items():
            self.requiredInstruments[k].addr = v
        self.found = self._find()

    def _find(self):
        self._instruments = {
            k: v.find()
            for k, v in self.requiredInstruments.items()
        }
        return all(self._instruments.values())

    def check(self, token, params):
        print(f'call check with {token} {params}')
        device, secondary = params
        self.present = self._check(token, device, secondary)
        print('sample pass')

    def _check(self, token, device, secondary):
        print(
            f'launch check with {self.deviceParams[device]} {self.secondaryParams}'
        )
        self._init()
        return True

    def calibrate(self, token, params):
        print(f'call calibrate with {token} {params}')
        return self._calibrate(token, self.secondaryParams)

    def _calibrateLO(self, token, secondary):
        print('run calibrate LO with', secondary)

        gen_lo = self._instruments['P LO']
        sa = self._instruments['Анализатор']

        secondary = self.secondaryParams

        pow_lo_start = secondary['Plo_min']
        pow_lo_end = secondary['Plo_max']
        pow_lo_step = secondary['Plo_delta']
        freq_lo_start = secondary['Flo_min']
        freq_lo_end = secondary['Flo_max']
        freq_lo_step = secondary['Flo_delta']
        freq_lo_x2 = secondary['is_Flo_x2']

        pow_lo_values = [round(x, 3) for x in np.arange(start=pow_lo_start, stop=pow_lo_end + 0.002, step=pow_lo_step)] \
            if pow_lo_start != pow_lo_end else [pow_lo_start]
        freq_lo_values = [
            round(x, 3) for x in np.arange(start=freq_lo_start,
                                           stop=freq_lo_end + 0.0001,
                                           step=freq_lo_step)
        ]

        sa.send(':CAL:AUTO OFF')
        sa.send(':SENS:FREQ:SPAN 1MHz')
        sa.send(f'DISP:WIND:TRAC:Y:RLEV 10')
        sa.send(f'DISP:WIND:TRAC:Y:PDIV 5')

        gen_lo.send(f':OUTP:MOD:STAT OFF')

        sa.send(':CALC:MARK1:MODE POS')

        result = defaultdict(dict)
        for pow_lo in pow_lo_values:
            gen_lo.send(f'SOUR:POW {pow_lo}dbm')

            for freq in freq_lo_values:

                if freq_lo_x2:
                    freq *= 2

                if token.cancelled:
                    gen_lo.send(f'OUTP:STAT OFF')
                    time.sleep(0.5)

                    gen_lo.send(f'SOUR:POW {pow_lo}dbm')

                    gen_lo.send(f'SOUR:FREQ {freq_lo_start}GHz')
                    raise RuntimeError('calibration cancelled')

                gen_lo.send(f'SOUR:FREQ {freq}GHz')
                gen_lo.send(f'OUTP:STAT ON')

                if not mock_enabled:
                    time.sleep(0.35)

                sa.send(f':SENSe:FREQuency:CENTer {freq}GHz')
                sa.send(f':CALCulate:MARKer1:X:CENTer {freq}GHz')

                if not mock_enabled:
                    time.sleep(0.35)

                pow_read = float(sa.query(':CALCulate:MARKer:Y?'))
                loss = abs(pow_lo - pow_read)
                if mock_enabled:
                    loss = 10

                print('loss: ', loss)
                result[pow_lo][freq] = loss

        result = {k: v for k, v in result.items()}
        pprint_to_file('cal_lo.ini', result)

        gen_lo.send(f'OUTP:STAT OFF')
        sa.send(':CAL:AUTO ON')
        self._calibrated_pows_lo = result
        return True

    def _calibrateRF(self, token, secondary):
        print('run calibrate RF with', secondary)

        gen_rf = self._instruments['P RF']
        sa = self._instruments['Анализатор']

        secondary = self.secondaryParams

        pow_rf = secondary['Prf']

        freq_rf_start = secondary['Frf_min']
        freq_rf_end = secondary['Frf_max']
        freq_rf_step = secondary['Frf_delta']

        freq_rf_values = [
            round(x, 3) for x in np.arange(start=freq_rf_start,
                                           stop=freq_rf_end + 0.0001,
                                           step=freq_rf_step)
        ]

        sa.send(':CAL:AUTO OFF')
        sa.send(':SENS:FREQ:SPAN 1MHz')
        sa.send(f'DISP:WIND:TRAC:Y:RLEV 10')
        sa.send(f'DISP:WIND:TRAC:Y:PDIV 5')

        # gen_rf.send(f':OUTP:MOD:STAT OFF')
        gen_rf.send(f'SOUR:POW {pow_rf}dbm')

        sa.send(':CALC:MARK1:MODE POS')

        result = {}
        for freq in freq_rf_values:

            if token.cancelled:
                gen_rf.send(f'OUTP:STAT OFF')
                time.sleep(0.5)

                gen_rf.send(f'SOUR:POW {pow_rf}dbm')

                gen_rf.send(f'SOUR:FREQ {freq_rf_start}GHz')
                raise RuntimeError('calibration cancelled')

            gen_rf.send(f'SOUR:FREQ {freq}GHz')
            gen_rf.send(f'OUTP:STAT ON')

            if not mock_enabled:
                time.sleep(0.35)

            sa.send(f':SENSe:FREQuency:CENTer {freq}GHz')
            sa.send(f':CALCulate:MARKer1:X:CENTer {freq}GHz')

            if not mock_enabled:
                time.sleep(0.35)

            pow_read = float(sa.query(':CALCulate:MARKer:Y?'))
            loss = abs(pow_rf - pow_read)
            if mock_enabled:
                loss = 10

            print('loss: ', loss)
            result[freq] = loss

        pprint_to_file('cal_rf.ini', result)

        gen_rf.send(f'OUTP:STAT OFF')
        sa.send(':CAL:AUTO ON')
        self._calibrated_pows_rf = result
        return True

    def measure(self, token, params):
        print(f'call measure with {token} {params}')
        device, _ = params
        try:
            self.result.set_secondary_params(self.secondaryParams)
            self._measure(token, device)
            # self.hasResult = bool(self.result)
            self.hasResult = True  # HACK
        except RuntimeError as ex:
            print('runtime error:', ex)

    def _measure(self, token, device):
        param = self.deviceParams[device]
        secondary = self.secondaryParams
        print(f'launch measure with {token} {param} {secondary}')

        self._clear()
        self._measure_s_params(token, param, secondary)
        return True

    def _clear(self):
        self.result.clear()

    def _init(self):
        self._instruments['P LO'].send('*RST')
        self._instruments['P RF'].send('*RST')
        self._instruments['Осциллограф'].send('*RST')
        self._instruments['Источник'].send('*RST')
        self._instruments['Мультиметр'].send('*RST')
        # self._instruments['Анализатор'].send('*RST')

    def _measure_s_params(self, token, param, secondary):
        gen_lo = self._instruments['P LO']
        gen_rf = self._instruments['P RF']
        osc = self._instruments['Осциллограф']
        src = self._instruments['Источник']
        mult = self._instruments['Мультиметр']
        # sa = self._instruments['Анализатор']

        src_u = secondary['Usrc']
        src_i = 200  # mA
        pow_lo_start = secondary['Plo_min']
        pow_lo_end = secondary['Plo_max']
        pow_lo_step = secondary['Plo_delta']
        freq_lo_start = secondary['Flo_min']
        freq_lo_end = secondary['Flo_max']
        freq_lo_step = secondary['Flo_delta']
        freq_lo_x2 = secondary['is_Flo_x2']

        pow_rf = secondary['Prf']
        freq_rf_start = secondary['Frf_min']
        freq_rf_end = secondary['Frf_max']
        freq_rf_step = secondary['Frf_delta']

        loss = secondary['loss']

        osc_avg = 'ON' if secondary['OscAvg'] else 'OFF'

        osc_scale = secondary['scale_y']
        osc_timebase_coeff = secondary['timebase_coeff']

        src.send(f'APPLY p6v,{src_u}V,{src_i}mA')

        osc.send(f':ACQ:AVERage {osc_avg}')

        osc.send(f':CHANnel1:DISPlay ON')
        osc.send(f':CHANnel2:DISPlay ON')

        osc.send(':CHAN1:SCALE 0.05')  # V
        osc.send(':CHAN2:SCALE 0.05')
        osc.send(':TIMEBASE:SCALE 10E-8')  # ms / div

        osc.send(':TRIGger:MODE EDGE')
        osc.send(':TRIGger:EDGE:SOURCe CHANnel1')
        osc.send(':TRIGger:LEVel CHANnel1,0')
        osc.send(':TRIGger:EDGE:SLOPe POSitive')

        osc.send(':MEASure:VAMPlitude channel1')
        osc.send(':MEASure:VAMPlitude channel2')
        osc.send(':MEASure:PHASe CHANnel1,CHANnel2')
        osc.send(':MEASure:FREQuency CHANnel1')

        # pow_lo_end = -5.0
        # pow_lo_step = 5

        pow_lo_values = [round(x, 3) for x in np.arange(start=pow_lo_start, stop=pow_lo_end + 0.0001, step=pow_lo_step)] \
            if pow_lo_start != pow_lo_end else [pow_lo_start]
        freq_lo_values = [
            round(x, 3) for x in np.arange(start=freq_lo_start,
                                           stop=freq_lo_end + 0.0001,
                                           step=freq_lo_step)
        ]
        freq_rf_values = [
            round(x, 3) for x in np.arange(start=freq_rf_start,
                                           stop=freq_rf_end + 0.0001,
                                           step=freq_rf_step)
        ]

        gen_lo.send(f':OUTP:MOD:STAT OFF')
        # gen_rf.send(f':OUTP:MOD:STAT OFF')

        low_signal_threshold = 1.1
        range_ratio = 1.2

        if mock_enabled:
            # with open('./mock_data/meas_1_-10-5db.txt', mode='rt', encoding='utf-8') as f:
            with open('./mock_data/meas_1_-10db.txt',
                      mode='rt',
                      encoding='utf-8') as f:
                index = 0
                mocked_raw_data = ast.literal_eval(''.join(f.readlines()))

        res = []
        for pow_lo in pow_lo_values:

            for freq_lo, freq_rf in zip(freq_lo_values, freq_rf_values):

                if freq_lo_x2:
                    freq_lo *= 2

                if token.cancelled:
                    gen_lo.send(f'OUTP:STAT OFF')
                    gen_rf.send(f'OUTP:STAT OFF')
                    time.sleep(0.5)
                    src.send('OUTPut OFF')

                    gen_rf.send(f'SOUR:POW {pow_rf}dbm')
                    gen_lo.send(f'SOUR:POW {pow_lo_start}dbm')

                    gen_rf.send(f'SOUR:FREQ {freq_rf_start}GHz')
                    gen_lo.send(f'SOUR:FREQ {freq_rf_start}GHz')
                    raise RuntimeError('measurement cancelled')

                gen_lo.send(
                    f'SOUR:POW {round(pow_lo + self._calibrated_pows_lo.get(pow_lo, dict()).get(freq_lo, 0) / 2, 2)}dbm'
                )
                gen_rf.send(
                    f'SOUR:POW {round(pow_rf + self._calibrated_pows_rf.get(freq_rf, 0) / 2, 2)}dbm'
                )

                gen_lo.send(f'SOUR:FREQ {freq_lo}GHz')
                gen_rf.send(f'SOUR:FREQ {freq_rf}GHz')

                # TODO hoist out of the loops
                src.send('OUTPut ON')

                gen_lo.send(f'OUTP:STAT ON')
                gen_rf.send(f'OUTP:STAT ON')

                osc.send(':CDISplay')

                # time.sleep(0.5)
                if not mock_enabled:
                    time.sleep(2)

                # read amp values
                if mock_enabled:
                    _, stats = mocked_raw_data[index]
                else:
                    stats = osc.query(':MEASure:RESults?')

                stats_split = stats.split(',')
                osc_ch1_amp = float(stats_split[18])
                osc_ch2_amp = float(stats_split[25])
                osc_phase = float(stats_split[11])
                osc_ch1_freq = float(stats_split[4])

                timebase = (1 /
                            (abs(freq_rf -
                                 ((freq_lo / 2) if freq_lo_x2 else freq_lo)) *
                             10_000_000)) * 0.01 * osc_timebase_coeff
                osc.send(f':TIMEBASE:SCALE {timebase}')  # ms / div
                osc.send(f':CHANnel1:OFFSet 0')
                osc.send(f':CHANnel2:OFFSet 0')

                max_amp = osc_ch1_amp if osc_ch1_amp > osc_ch2_amp else osc_ch2_amp

                if not mock_enabled:
                    # check if auto-scale is needed:
                    # some of the measure points go out of OSC display range resulting in incorrect measurement
                    # this is correct external device behaviour, not a program bug
                    if max_amp < 1_000_000:
                        # if reading is correct, check if the signal is too small
                        big_amp, ch_num = (
                            osc_ch1_amp,
                            1) if osc_ch1_amp > osc_ch2_amp else (osc_ch2_amp,
                                                                  2)
                        current_scale = float(
                            osc.query(f':CHAN{ch_num}:SCALE?'))

                        # if signal fits in less than 1.5 sections of the display, is is too small, need to
                        # auto scale OSC display up
                        while big_amp / current_scale <= low_signal_threshold:

                            if token.cancelled:
                                gen_lo.send(f'OUTP:STAT OFF')
                                gen_rf.send(f'OUTP:STAT OFF')
                                time.sleep(0.5)
                                src.send('OUTPut OFF')

                                gen_rf.send(f'SOUR:POW {pow_rf}dbm')
                                gen_lo.send(f'SOUR:POW {pow_lo_start}dbm')

                                gen_rf.send(f'SOUR:FREQ {freq_rf_start}GHz')
                                gen_lo.send(f'SOUR:FREQ {freq_rf_start}GHz')
                                raise RuntimeError('measurement cancelled')

                            target_range = big_amp + big_amp * range_ratio

                            osc.send(f':CHANnel1:RANGe {target_range}')
                            osc.send(f':CHANnel2:RANGe {target_range}')

                            osc.send(':CDIS')

                            time.sleep(2)

                            autofit_stats_split = osc.query(
                                ':MEASure:RESults?').split(',')

                            osc_ch1_amp = float(autofit_stats_split[18])
                            osc_ch2_amp = float(autofit_stats_split[25])

                            big_amp, ch_num = (
                                osc_ch1_amp,
                                1) if osc_ch1_amp > osc_ch2_amp else (
                                    osc_ch2_amp, 2)
                            current_scale = float(
                                osc.query(f':CHAN{ch_num}:SCALE?'))
                    # TODO fix this branch for small signal behaviour
                    else:
                        # if reading was not correct, reset OSC display range to safe level (controlled via GUI)
                        # and iterate OSC range scaling a few times
                        # to get the correct reading
                        max_amp = osc_ch1_amp if osc_ch1_amp > osc_ch2_amp else osc_ch2_amp
                        if max_amp > 1_000_000:

                            osc.send(f':CHANnel1:RANGe {osc_scale}')
                            osc.send(f':CHANnel2:RANGe {osc_scale}')

                            osc.send(':CDIS')

                            time.sleep(2)

                            autofit_stats_split = osc.query(
                                ':MEASure:RESults?').split(',')
                            osc_ch1_amp = float(autofit_stats_split[18])
                            osc_ch2_amp = float(autofit_stats_split[25])

                            # check if safe level results in too small signal
                            big_amp, ch_num = (
                                osc_ch1_amp,
                                1) if osc_ch1_amp > osc_ch2_amp else (
                                    osc_ch2_amp, 2)
                            current_scale = float(
                                osc.query(f':CHAN{ch_num}:SCALE?'))

                            # if signal fits in less than 1.5 sections of the display, auto scale OSC display up
                            while big_amp / current_scale <= low_signal_threshold:

                                if token.cancelled:
                                    gen_lo.send(f'OUTP:STAT OFF')
                                    gen_rf.send(f'OUTP:STAT OFF')
                                    time.sleep(0.5)
                                    src.send('OUTPut OFF')

                                    gen_rf.send(f'SOUR:POW {pow_rf}dbm')
                                    gen_lo.send(f'SOUR:POW {pow_lo_start}dbm')

                                    gen_rf.send(
                                        f'SOUR:FREQ {freq_rf_start}GHz')
                                    gen_lo.send(
                                        f'SOUR:FREQ {freq_rf_start}GHz')
                                    raise RuntimeError('measurement cancelled')

                                target_range = big_amp + big_amp * range_ratio

                                osc.send(f':CHANnel1:RANGe {target_range}')
                                osc.send(f':CHANnel2:RANGe {target_range}')

                                osc.send(':CDIS')

                                time.sleep(2)

                                autofit_stats_split = osc.query(
                                    ':MEASure:RESults?').split(',')

                                osc_ch1_amp = float(autofit_stats_split[18])
                                osc_ch2_amp = float(autofit_stats_split[25])

                                big_amp, ch_num = (
                                    osc_ch1_amp,
                                    1) if osc_ch1_amp > osc_ch2_amp else (
                                        osc_ch2_amp, 2)
                                current_scale = float(
                                    osc.query(f':CHAN{ch_num}:SCALE?'))
                            else:
                                # if safe level is acceptable, select largest signal
                                # and fit the display to 130% of the signal
                                max_amp = osc_ch1_amp if osc_ch1_amp > osc_ch2_amp else osc_ch2_amp
                                target_range = max_amp * range_ratio
                                if target_range < 1_000_000:
                                    osc.send(f':CHANnel1:RANGe {target_range}')
                                    osc.send(f':CHANnel2:RANGe {target_range}')

                # read actual amp values after auto-scale (if any occured)
                osc.send(':CDIS')

                if not mock_enabled:
                    time.sleep(2)

                if mock_enabled:
                    _, stats = mocked_raw_data[index]
                else:
                    stats = osc.query(':MEASure:RESults?')
                stats_split = stats.split(',')
                osc_ch1_amp = float(stats_split[18])
                osc_ch2_amp = float(stats_split[25])

                f_lo_read = float(gen_lo.query('SOUR:FREQ?'))
                f_rf_read = float(gen_rf.query('SOUR:FREQ?'))

                i_src_read = float(mult.query('MEAS:CURR:DC? 1A,DEF'))

                raw_point = {
                    'p_lo': pow_lo,
                    'f_lo': f_lo_read,
                    'p_rf': pow_rf,
                    'f_rf': f_rf_read,
                    'u_src': src_u,  # power source voltage
                    'i_src': i_src_read,
                    'ch1_amp': osc_ch1_amp,
                    'ch2_amp': osc_ch2_amp,
                    'phase': osc_phase,
                    'ch1_freq': osc_ch1_freq,
                    'loss': loss,
                }

                if mock_enabled:
                    raw_point, stats = mocked_raw_data[index]
                    raw_point['loss'] = loss
                    index += 1

                print(raw_point, stats)
                self._add_measure_point(raw_point)

                res.append([raw_point, stats])

        gen_lo.send(f'OUTP:STAT OFF')
        gen_rf.send(f'OUTP:STAT OFF')
        time.sleep(0.5)
        src.send('OUTPut OFF')

        gen_rf.send(f'SOUR:POW {pow_rf}dbm')
        gen_lo.send(f'SOUR:POW {pow_lo_start}dbm')

        gen_rf.send(f'SOUR:FREQ {freq_rf_start}GHz')
        gen_lo.send(f'SOUR:FREQ {freq_rf_start}GHz')

        if not mock_enabled:
            with open('out.txt', mode='wt', encoding='utf-8') as f:
                f.write(str(res))

        return res

    def _add_measure_point(self, data):
        print('measured point:', data)
        self.result.add_point(data)
        self.pointReady.emit()

    def saveConfigs(self):
        pprint_to_file('params.ini', self.secondaryParams)

    @pyqtSlot(dict)
    def on_secondary_changed(self, params):
        self.secondaryParams = params

    @property
    def status(self):
        return [i.status for i in self._instruments.values()]
Exemplo n.º 2
0
class InstrumentController(QObject):
    pointReady = pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent=parent)

        addrs = load_ast_if_exists('instr.ini',
                                   default={
                                       'Анализатор': 'GPIB1::18::INSTR',
                                       'P LO': 'GPIB1::6::INSTR',
                                       'P RF': 'GPIB1::20::INSTR',
                                       'Источник': 'GPIB1::3::INSTR',
                                       'Мультиметр': 'GPIB1::22::INSTR',
                                   })

        self.requiredInstruments = {
            'Анализатор': AnalyzerFactory(addrs['Анализатор']),
            'P LO': GeneratorFactory(addrs['P LO']),
            'P RF': GeneratorFactory(addrs['P RF']),
            'Источник': SourceFactory(addrs['Источник']),
            'Мультиметр': MultimeterFactory(addrs['Мультиметр']),
        }

        self.deviceParams = {
            'Демодулятор': {
                'F': 1,
            },
        }

        self.secondaryParams = load_ast_if_exists('params.ini',
                                                  default={
                                                      'Frf_delta': 0.5,
                                                      'Frf_max': 3.06,
                                                      'Frf_min': 0.06,
                                                      'Prf_delta': 2.0,
                                                      'Prf_max': 6.0,
                                                      'Prf_min': -20.0,
                                                      'Flo_delta': 0.5,
                                                      'Flo_max': 3.05,
                                                      'Flo_min': 0.05,
                                                      'is_Flo_x2': False,
                                                      'Plo': -5.0,
                                                      'Usrc': 5.0,
                                                      'loss': 0.82,
                                                      'ref_lev': 10.0,
                                                      'scale_y': 5.0,
                                                  })

        self._calibrated_pows_lo = load_ast_if_exists('cal_lo.ini', default={})
        self._calibrated_pows_rf = load_ast_if_exists('cal_rf.ini', default={})

        self._instruments = dict()
        self.found = False
        self.present = False
        self.hasResult = False
        self.only_main_states = False

        self.result = MeasureResult()

    def __str__(self):
        return f'{self._instruments}'

    def connect(self, addrs):
        print(f'searching for {addrs}')
        for k, v in addrs.items():
            self.requiredInstruments[k].addr = v
        self.found = self._find()

    def _find(self):
        self._instruments = {
            k: v.find()
            for k, v in self.requiredInstruments.items()
        }
        return all(self._instruments.values())

    def check(self, token, params):
        print(f'call check with {token} {params}')
        device, secondary = params
        self.present = self._check(token, device, secondary)
        print('sample pass')

    def _check(self, token, device, secondary):
        print(
            f'launch check with {self.deviceParams[device]} {self.secondaryParams}'
        )
        self._init()
        return True

    def _calibrateLO(self, token, secondary):
        print('run calibrate LO with', secondary)

        gen_lo = self._instruments['P LO']
        sa = self._instruments['Анализатор']

        secondary = self.secondaryParams

        pow_lo = secondary['Plo']
        freq_lo_start = secondary['Flo_min']
        freq_lo_end = secondary['Flo_max']
        freq_lo_step = secondary['Flo_delta']
        freq_lo_x2 = secondary['is_Flo_x2']

        freq_lo_values = [
            round(x, 3) for x in np.arange(start=freq_lo_start,
                                           stop=freq_lo_end + 0.0001,
                                           step=freq_lo_step)
        ]

        sa.send(':CAL:AUTO OFF')
        sa.send(':SENS:FREQ:SPAN 1MHz')
        sa.send(f'DISP:WIND:TRAC:Y:RLEV 10')
        sa.send(f'DISP:WIND:TRAC:Y:PDIV 5')
        sa.send(':CALC:MARK1:MODE POS')

        gen_lo.send(f':OUTP:MOD:STAT OFF')
        gen_lo.send(f'SOUR:POW {pow_lo}dbm')

        result = {}
        for freq in freq_lo_values:

            if freq_lo_x2:
                freq *= 2

            if token.cancelled:
                gen_lo.send(f'OUTP:STAT OFF')
                time.sleep(0.5)

                gen_lo.send(f'SOUR:POW {pow_lo}dbm')

                gen_lo.send(f'SOUR:FREQ {freq_lo_start}GHz')
                raise RuntimeError('calibration cancelled')

            gen_lo.send(f'SOUR:FREQ {freq}GHz')
            gen_lo.send(f'OUTP:STAT ON')

            if not mock_enabled:
                time.sleep(0.35)

            sa.send(f':SENSe:FREQuency:CENTer {freq}GHz')
            sa.send(f':CALCulate:MARKer1:X:CENTer {freq}GHz')

            if not mock_enabled:
                time.sleep(0.35)

            pow_read = float(sa.query(':CALCulate:MARKer:Y?'))
            loss = abs(pow_lo - pow_read)
            if mock_enabled:
                loss = 10

            print('loss: ', loss)
            result[freq] = loss

        pprint_to_file('cal_lo.ini', result)

        gen_lo.send(f'OUTP:STAT OFF')
        sa.send(':CAL:AUTO ON')
        self._calibrated_pows_lo = result
        return True

    def _calibrateRF(self, token, secondary):
        print('run calibrate RF with', secondary)

        gen_rf = self._instruments['P RF']
        sa = self._instruments['Анализатор']

        secondary = self.secondaryParams

        pow_rf_start = secondary['Prf_min']
        pow_rf_end = secondary['Prf_max']
        pow_rf_step = secondary['Prf_delta']

        freq_rf_start = secondary['Frf_min']
        freq_rf_end = secondary['Frf_max']
        freq_rf_step = secondary['Frf_delta']

        pow_rf_values = [
            round(x, 3) for x in np.arange(
                start=pow_rf_start, stop=pow_rf_end + 0.002, step=pow_rf_step)
        ]
        freq_rf_values = [
            round(x, 3) for x in np.arange(start=freq_rf_start,
                                           stop=freq_rf_end + 0.002,
                                           step=freq_rf_step)
        ]

        sa.send(':CAL:AUTO OFF')
        sa.send(':SENS:FREQ:SPAN 1MHz')
        sa.send(f'DISP:WIND:TRAC:Y:RLEV 10')
        sa.send(f'DISP:WIND:TRAC:Y:PDIV 5')
        sa.send(':CALC:MARK1:MODE POS')

        result = defaultdict(dict)
        for freq in freq_rf_values:
            gen_rf.send(f'SOUR:FREQ {freq}GHz')

            for pow_rf in pow_rf_values:
                if token.cancelled:
                    gen_rf.send(f'OUTP:STAT OFF')

                    time.sleep(0.5)

                    gen_rf.send(f'SOUR:POW {pow_rf_start}dbm')
                    gen_rf.send(f'SOUR:FREQ {freq_rf_start}GHz')
                    raise RuntimeError('calibration cancelled')

                gen_rf.send(f'SOUR:POW {pow_rf}dbm')
                gen_rf.send(f'OUTP:STAT ON')

                if not mock_enabled:
                    time.sleep(0.35)

                sa.send(f':SENSe:FREQuency:CENTer {freq}GHz')
                sa.send(f':CALCulate:MARKer1:X:CENTer {freq}GHz')

                if not mock_enabled:
                    time.sleep(0.35)

                pow_read = float(sa.query(':CALCulate:MARKer:Y?'))
                loss = abs(pow_rf - pow_read)
                if mock_enabled:
                    loss = 10

                print('loss: ', loss)
                result[freq][pow_rf] = loss

        result = {k: v for k, v in result.items()}
        pprint_to_file('cal_rf.ini', result)

        gen_rf.send(f'OUTP:STAT OFF')
        sa.send(':CAL:AUTO ON')
        self._calibrated_pows_rf = result
        return True

    def measure(self, token, params):
        print(f'call measure with {token} {params}')
        device, _ = params
        try:
            self.result.set_secondary_params(self.secondaryParams)
            self._measure(token, device)
            # self.hasResult = bool(self.result)
            self.hasResult = True  # HACK
        except RuntimeError as ex:
            print('runtime error:', ex)

    def _measure(self, token, device):
        param = self.deviceParams[device]
        secondary = self.secondaryParams
        print(f'launch measure with {token} {param} {secondary}')

        self._clear()
        self._measure_s_params(token, param, secondary)
        return True

    def _clear(self):
        self.result.clear()

    def _init(self):
        self._instruments['P LO'].send('*RST')
        self._instruments['P RF'].send('*RST')
        self._instruments['Источник'].send('*RST')
        self._instruments['Мультиметр'].send('*RST')
        self._instruments['Анализатор'].send('*RST')

    def _measure_s_params(self, token, param, secondary):
        gen_lo = self._instruments['P LO']
        gen_rf = self._instruments['P RF']
        src = self._instruments['Источник']
        mult = self._instruments['Мультиметр']
        sa = self._instruments['Анализатор']

        src_u = secondary['Usrc']
        src_i = 200  # mA

        pow_lo = secondary['Plo']
        freq_lo_start = secondary['Flo_min']
        freq_lo_end = secondary['Flo_max']
        freq_lo_step = secondary['Flo_delta']
        freq_lo_x2 = secondary['is_Flo_x2']

        pow_rf_start = secondary['Prf_min']
        pow_rf_end = secondary['Prf_max']
        pow_rf_step = secondary['Prf_delta']

        freq_rf_start = secondary['Frf_min']
        freq_rf_end = secondary['Frf_max']
        freq_rf_step = secondary['Frf_delta']

        ref_level = secondary['ref_lev']
        scale_y = secondary['scale_y']

        p_loss = secondary['loss']

        pow_rf_values = [
            round(x, 3) for x in np.arange(
                start=pow_rf_start, stop=pow_rf_end + 0.002, step=pow_rf_step)
        ]
        freq_lo_values = [
            round(x, 3) for x in np.arange(start=freq_lo_start,
                                           stop=freq_lo_end + 0.002,
                                           step=freq_lo_step)
        ]
        freq_rf_values = [
            round(x, 3) for x in np.arange(start=freq_rf_start,
                                           stop=freq_rf_end + 0.002,
                                           step=freq_rf_step)
        ]

        src.send(f'APPLY p6v,{src_u}V,{src_i}mA')

        sa.send(':CAL:AUTO OFF')
        sa.send(':SENS:FREQ:SPAN 1MHz')
        sa.send(f'DISP:WIND:TRAC:Y:RLEV {ref_level}')
        sa.send(f'DISP:WIND:TRAC:Y:PDIV {scale_y}')

        gen_lo.send(f':OUTP:MOD:STAT OFF')

        if mock_enabled:
            with open('./mock_data/-5db.txt', mode='rt',
                      encoding='utf-8') as f:
                index = 0
                mocked_raw_data = ast.literal_eval(''.join(f.readlines()))

        res = []
        for freq_lo, freq_rf in zip(freq_lo_values, freq_rf_values):

            freq_rf_label = float(freq_rf)
            if freq_lo_x2:
                freq_lo *= 2

            gen_lo.send(f'SOUR:FREQ {freq_lo}GHz')

            delta_lo = round(self._calibrated_pows_lo.get(freq_lo, 0) / 2, 2)
            print('delta LO:', delta_lo)
            gen_lo.send(f'SOUR:POW {pow_lo + delta_lo}dbm')

            gen_rf.send(f'SOUR:FREQ {freq_rf}GHz')

            for pow_rf in pow_rf_values:

                if token.cancelled:
                    gen_lo.send(f'OUTP:STAT OFF')
                    gen_rf.send(f'OUTP:STAT OFF')

                    if not mock_enabled:
                        time.sleep(0.5)

                    src.send('OUTPut OFF')

                    gen_rf.send(f'SOUR:POW {pow_rf_start}dbm')
                    gen_lo.send(f'SOUR:POW {pow_lo}dbm')

                    gen_rf.send(f'SOUR:FREQ {freq_rf_start}GHz')
                    gen_lo.send(f'SOUR:FREQ {freq_rf_start}GHz')

                    sa.send(':CAL:AUTO ON')
                    raise RuntimeError('measurement cancelled')

                delta_rf = round(
                    self._calibrated_pows_rf.get(freq_rf, dict()).get(
                        pow_rf, 0) / 2, 2)
                print('delta RF:', delta_rf)
                gen_rf.send(f'SOUR:POW {pow_rf + delta_rf}dbm')

                src.send('OUTPut ON')

                gen_lo.send(f'OUTP:STAT ON')
                gen_rf.send(f'OUTP:STAT ON')

                time.sleep(0.1)
                if not mock_enabled:
                    time.sleep(0.5)

                i_mul_read = float(mult.query('MEAS:CURR:DC? 1A,DEF'))

                center_freq = (freq_rf -
                               freq_lo) if not freq_lo_x2 else (freq_rf -
                                                                freq_lo / 2)
                sa.send(':CALC:MARK1:MODE POS')
                sa.send(f':SENSe:FREQuency:CENTer {center_freq}GHz')
                sa.send(f':CALCulate:MARKer1:X:CENTer {center_freq}GHz')

                if not mock_enabled:
                    time.sleep(0.5)

                pow_read = float(sa.query(':CALCulate:MARKer:Y?'))

                raw_point = {
                    'f_lo': freq_lo,
                    'f_rf_label': freq_rf_label,
                    'f_rf': freq_rf,
                    'p_lo': pow_lo,
                    'p_rf': pow_rf,
                    'u_mul': src_u,
                    'i_mul': i_mul_read,
                    'pow_read': pow_read,
                    'loss': p_loss,
                }

                if mock_enabled:
                    raw_point = mocked_raw_data[index]
                    raw_point['loss'] = p_loss
                    raw_point['f_rf_label'] = freq_rf_label
                    index += 1

                print(raw_point)
                self._add_measure_point(raw_point)

                res.append(raw_point)

        if not mock_enabled:
            with open('out.txt', mode='wt', encoding='utf-8') as f:
                f.write(str(res))

        gen_lo.send(f'OUTP:STAT OFF')
        gen_rf.send(f'OUTP:STAT OFF')

        if not mock_enabled:
            time.sleep(0.5)

        src.send('OUTPut OFF')

        gen_rf.send(f'SOUR:POW {pow_rf_start}dbm')
        gen_lo.send(f'SOUR:POW {pow_lo}dbm')

        gen_rf.send(f'SOUR:FREQ {freq_rf_start}GHz')
        gen_lo.send(f'SOUR:FREQ {freq_rf_start}GHz')

        sa.send(':CAL:AUTO ON')
        return res

    def _add_measure_point(self, data):
        print('measured point:', data)
        self.result.add_point(data)
        self.pointReady.emit()

    def saveConfigs(self):
        pprint_to_file('params.ini', self.secondaryParams)

    @pyqtSlot(dict)
    def on_secondary_changed(self, params):
        self.secondaryParams = params

    @property
    def status(self):
        return [i.status for i in self._instruments.values()]