VNA.py 14.4 KB
Newer Older
1
2
3
4
5
6
7
# -*- coding: utf-8 -*-
"""
Module for VNA Measurements

Author: Christian Schneider <c.schneider@uibk.ac.at>
Date: 07.09.2017
"""
8
from sqlalchemy.exc import StatementError
9
10

import DataModule as dm
11
import numpy as np
12
import time
13
14
from .Drivers.Keysight.E5080 import E5080
from .Drivers.Keysight.E5071C import E5071C
15
16
from IPLIST import IPList
import SensorReader as SR
17
18
19
20
from Monitor.main import db, Instruments
import getpass
from Monitor.functions import retrieve_name
import pickle
21

22
23
24
25
26
27
28
29

class VNA(object):
    """VNA Instrument class.

    Provides an object to measure with the VNAs in the Kirchmair Lab.

    Parameters
    -----------
Christian Schneider's avatar
Christian Schneider committed
30
    id_string : str
31
32
33
        Name of the VNA. Right now choose between 'VNA1', 'VNA2' or 'VNA3'.
        You can also specify an IP, however then you have to manually specify
        the driver for the chosen IP address
Christian Schneider's avatar
Christian Schneider committed
34
    cryostat : str
35
36
37
38
39
        Name of the Cryostat. Choose between 'Freezer' and 'Cooler'
    driver : object, optional
        Driver for the VNA. Only needed if VNA is not already in IPList, or
        you specified it with an IP.
    """
Christian Schneider's avatar
Christian Schneider committed
40
    def __init__(self, id_string, cryostat, driver=None):
41
        try:
Christian Schneider's avatar
Christian Schneider committed
42
            self.ip = IPList[id_string]
43
44
        except KeyError:
            # If VNA is not specified by valid IPList entry. Try directly ip
45
            if driver is not None:
Christian Schneider's avatar
Christian Schneider committed
46
                self.ip = id_string
47
48
            else:
                raise Exception('VNA not in IPList. Please specify driver')
49
50
51
52
53
        self.CryoID = cryostat

        # New for DB
        self.ch = 1  # Not required here, however added for compatibility to CS
        self.dB_ID = None
Christian Schneider's avatar
Christian Schneider committed
54
        self.id = id_string
55
        self.check_db()
56
57

        # Load driver
Christian Schneider's avatar
Christian Schneider committed
58
        if id_string == 'VNA3' or id_string == "VNA4":
59
            self.driver = E5080
Christian Schneider's avatar
Christian Schneider committed
60
        elif id_string == 'VNA1' or id_string == 'VNA2':
61
            self.driver = E5071C
62
63
64
65
        else:
            if driver is None:
                raise Exception('DriverError. Specify Driver')
            else:
66
67
                self.driver = driver

68
        self.instr = self.driver(self.ip)
69
        print("Identify: " + self.identify())  # Identify at beginning
Christian Schneider's avatar
Christian Schneider committed
70
        print("IP: " + self.ip)
71
72
73
74
        self.update_db()

    def identify(self):
        return self.instr.identify()
75

76
77
78
79
80
81
82
    # def meas(self, f_range, npoints=1601, navg=10, power=-50, Spar='S21',
    #          Format='MLOG', BW=1e3, autoSave=True,
    #          filename='measurements/measurement.dm',
    #          Use_Date=True, overwrite=False, Temp_reading=True,
    #          parameters={}, power_port2=False):
    #     """VNA Measurement
    #
83
    #     Automatically save as data_table datamodule if not specified
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
    #     otherwise (autoSave=False).
    #
    #     Parameters
    #     -----------
    #     f_range : array
    #         Frequency range [[f_start, f_stop]]
    #     npoints : int
    #         Number of Points
    #     navg : int
    #         Number of averages
    #     power : float
    #         VNA output power in dBm
    #     Spar : str
    #         S Parameter to measure. E.g. 'S21'
    #     Format : str
    #         Format for measurement. Choose from
    #          | 'MLOG' : magnitude in dB
    #          | 'PHAS': phase
    #          | 'MLIN': linear magnitude
    #          | 'REAL': real part of the complex data
    #          | 'IMAG' imaginary part of the complex data
    #          | 'UPH': Extended phase
    #          | 'PPH': Positive phase
    #     BW : int
    #         IF Bandwidth in Hz
    #     autosave : bool
    #         Automatically save data to disk (RECOMMENDED!!)
    #     filename : str
    #         Filepath + Filename. E.g. '/measurements/test'
    #     overwrite : bool
    #         Force override
    #     Temp_reading : bool
    #         Append temperature to datamodule file
    #     Use_Date : bool
    #         Add Date as leading part of filename
    #     parameters : dict
    #         Additional parameters to save in dm
    #     power_port2 : bool
    #         Power excitation on port2
    #
    #     Returns
    #     --------
    #     datamodule object
    #         Returns datamodule object if autoSave is set to False
    #     """
    #     # Initialize datamodule
130
    #     data = dm.data_table()
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
    #     data.time_start = time.strftime(data.date_format, time.localtime())
    #     if Temp_reading and self.CryoID:
    #         a = SR.SensorReader(self.CryoID)
    #         data.temp_start = a.base_temp()
    #
    #     # Measure
    #     f, d = self.instr.meas(f_range=f_range, npoints=npoints, navg=navg,
    #                            power=power, Spar=Spar, BW=BW, Format=Format,
    #                            power_port2=power_port2)
    #     # Save in datamodule
    #     data.time_stop = time.strftime(data.date_format, time.localtime())
    #     if Temp_reading and self.CryoID:
    #         data.temp_stop = a.base_temp()
    #     data.load_var(f, d)
    #     data.insert_par(frange=f_range, npoints=npoints, navg=navg,
    #                     power=power, Spar=Spar, BW=BW, **parameters)
    #
    #     # Save
    #     if (autoSave is True):
    #         data.save(filename, Use_Date, overwrite)
    #         self.update_db()
    #     else:
    #         self.update_db()
    #         return data
155
156

    def meas_complex_avg(self, f_range, npoints=1601, navg=10, power=-50,
157
158
159
                         Spar='S21', BW=1e3, autoSave=True,
                         filename='measurements/measurement.dm',
                         Use_Date=True, overwrite=False, Temp_reading=True,
Christian Schneider's avatar
Christian Schneider committed
160
                         parameters={}, power_port2=False):
161
162
163
164
165
166
167
        """VNA Measurement with complex data format.

        Automatically save as complex datamodule (Voltages) if not specified
        otherwise (autoSave=False).

        Parameters
        -----------
168
169
        f_range : [float, float]
            Frequency range in GHz. [f_start, f_stop]
170
171
172
173
174
175
176
177
178
179
        npoints : int
            Number of Points
        navg : int
            Number of averages
        power : float
            VNA output power in dBm
        Spar : str
            S Parameter to measure. E.g. 'S21'
        BW : int
            IF Bandwidth in Hz
Christian Schneider's avatar
Christian Schneider committed
180
        autoSave : bool
181
182
183
184
185
186
187
188
189
190
191
            Automatically save data to disk (RECOMMENDED!!)
        filename : str
            Filepath + Filename. E.g. '/measurements/test'
        overwrite : bool
            Force override
        Temp_reading : bool
            Append temperature to datamodule file
        Use_Date : bool
            Add Date as leading part of filename
        parameters : dict
            Additional parameters to save in dm
Christian Schneider's avatar
Christian Schneider committed
192
193
        power_port2 : bool
            Power excitation on port2
Christian Schneider's avatar
Christian Schneider committed
194
195

        Returns
196
197
198
        --------
        datamodule object
            Returns datamodule object if autoSave is set to False
199
200
        """
        # Initialize datamodule
201
        time_start = time.strftime('%Y-%m-%d-%H:%M:%S', time.localtime())
Christian Schneider's avatar
Christian Schneider committed
202
        cor = self.instr.correction()
203
        # Measure
204
205
206
207
208
209
210
211
        pars = {'f_start': '{:.4f} GHz'.format(f_range[0]),
                'f_stop': '{:.4f} GHz'.format(f_range[1]),
                'Power': '{:.0f} dBm'.format(power),
                'Points': '{:.0f}'.format(npoints),
                'Averages': '{:.0f}'.format(navg),
                'Format': '{}'.format("REAL"),
                'IF Bandwidth': '{:.2f} kHz'.format(BW/1e3),
                'RF Power': '{}'.format("ON"),
Christian Schneider's avatar
Christian Schneider committed
212
                'S-Parameter': '{}'.format(Spar),
213
                'Correction': '{}'.format('ON' if int(cor) == 1
214
215
                                          else 'OFF')
               }
Christian Schneider's avatar
Christian Schneider committed
216
        self.update_db(pars=pars) 
217
218
219
220
        f, v_re, v_im = self.instr.meas_complex(f_range=f_range,
                                                npoints=npoints, navg=navg,
                                                power=power, Spar=Spar,
                                                BW=BW,
221
                                                power_port2=power_port2)
222
        # Save in datamodule
223
        data = dm.data_complex(f, np.array(v_re) + 1j * np.array(v_im))
224
        data.time_start = time_start
225
        data.time_stop = time.strftime(data.date_format, time.localtime())
226
        if Temp_reading and self.CryoID:
227
228
            a = SR.SensorReader(self.CryoID)
            temp_start = a.base_temp()
229
            data.temp_start = temp_start
230
231
232
233
234
235
            data.temp_stop = a.base_temp()
        data.load_var(f, v_re, v_im)
        data.insert_par(frange=f_range, npoints=npoints, navg=navg,
                        power=power, Spar=Spar, BW=BW, **parameters)

        # Save
Christian Schneider's avatar
Christian Schneider committed
236
        if autoSave is True:
237
            data.save(filename, Use_Date, overwrite)
238
            self.update_db()
239
        else:
240
            self.update_db()
241
            return data
242
243
244
245

    def get_current_measurement_cplx(self):
        """Gets current complex measurement data and saves to cplx. datamodule
        """
Christian Schneider's avatar
Christian Schneider committed
246
        data = dm.data_complex()
247
248
249
250
251
252
253
254
255
256
        re = np.asarray(self.instr.trace_read(1)[0], dtype='float')
        im = np.asarray(self.instr.trace_read(2)[0], dtype='float')
        try:
            # Try to get frequency range (not supported by E5080)
            x = np.asarray(self.instr.freq_read(), dtype='float')
        except:
            # If not supported, return zeros for setting later manually
            print("Could not receive frequencies. Set to 0. Please"
                  "add manually")
            x = np.zeros_like(re)
257
258
259
260
261
262
        data.load_var(x/1e9, re, im)
        data.time_stop = time.strftime(data.date_format, time.localtime())
        return data

    def get_current_measurement(self):
        """Gets current measurement data and saves to datamodule"""
263
        dat = dm.data_table()
264
265
266
267
268
269
270
271
272
        y = np.asarray(self.instr.trace_read()[0], dtype='float')
        try:
            # Try to get frequency range (not supported by E5080)
            x = np.asarray(self.instr.freq_read(), dtype='float')
        except:
            # If not supported, return zeros for setting later manually
            print("Could not receive frequencies. Set to 0. Please"
                  "add manually")
            x = np.zeros_like(y)
273
274
275
        dat.load_var(x/1e9, y)
        dat.time_stop = time.strftime(dat.date_format, time.localtime())
        return dat
276
277
278
279
280
281
282
283
284
285
286
287

    # Database functions #######################################################
    def check_db(self):
        """A check if device is already in database and used by someone else
        """
        try:
            # Try to reach database
            d = Instruments.query.filter_by(ip=self.ip).filter_by(
                channel=self.ch).first()
            if d:
                if d.user != getpass.getuser():
                    # If you are not the user print Error
288

289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
                    user = d.user
                    db.session.commit()
                    raise Exception('Device already used by {user}. Please '
                                    'talk  to him and then delete the device '
                                    'from the Device Monitor'
                                    .format(user=user))
                else:
                    # If you are the user and the device is already in db,
                    # extract the id
                    self.dB_ID = d.id
                    db.session.commit()
        except StatementError:
            # Do nothing if database is not responding
            return None

304
305
    def get_parameters(self):
        pars = self.instr.read_settings()
306
307
308
309
310
311
312
313
314
315
        d = {'f_start': '{:.4f} GHz'.format(pars['f_start (GHz)']),
             'f_stop': '{:.4f} GHz'.format(pars['f_stop (GHz)']),
             'Power': '{:.0f} dBm'.format(pars['power (dBm)']),
             'Points': '{:.0f}'.format(pars['Points']),
             'Averages': '{:.0f}'.format(pars['averages']),
             'Format': '{}'.format(pars['Format']),
             'IF Bandwidth': '{:.2f} kHz'.format(pars['IF - BW (Hz)']/1e3),
             'RF Power': '{}'.format('ON' if int(pars['output']) == 1 else
                                     'OFF'),
             'S-Parameter': '{}'.format(pars['S_parameter ']),
316
317
             'Correction': '{}'.format('ON' if int (pars['correction']) == 1
                                       else 'OFF')
318
            }
319
320
        return d

321
    def update_db(self, update_fromWeb=False, pars=None):
322
323
324
325
326
327
328
329
330
331
332
333
334
        """Update parameters in database value. Handles events from the
        Device Monitor WebApp by the flag update_fromWeb=True
        """
        if self.dB_ID:
            try:
                # To ensure that the library is working even if there is no
                # connection to the database
                d = Instruments.query.get(int(self.dB_ID))
                if not update_fromWeb:
                    # Only Update if flag is not set. Useful for flask server
                    #  who would rename it to an unreadable name
                    d.varname = retrieve_name(self)
                    d.user = getpass.getuser()
335
336
337
338
                if not pars:
                    d.parameters = self.get_parameters()
                else:
                    d.parameters = pars
339
340
341
342
343
344
345
346
347
348
349
350
351
                db.session.add(d)
                db.session.commit()
                db.session.close()
            except StatementError:
                # Do nothing if database is not responding
                return None

        else:
            # Create db entry
            # Create check to warn if the device is already used and demand
            # to delete it
            # Socket is not pickleable, close connection and remove socket
            idn = self.identify()
352
            pars = self.get_parameters()
353
354
355
356
357
358
359
            self.instr.close()
            self.instr = None
            try:
                d = Instruments(ip=self.ip,
                                devtype='VNA',
                                name=self.id,
                                varname=self.id,
360
                                parameters=pars,
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
                                pckle=pickle.dumps(self),
                                user=getpass.getuser(),
                                idn=idn,
                                channel=self.ch)
                # Store to database
                db.session.add(d)
                db.session.commit()
                # Acquire ID
                self.dB_ID = d.id
                # Save Object with self.dB_ID tag
                d.pckle = pickle.dumps(self)
                db.session.commit()
                db.session.close()
                # Reinitialize device
                self.instr = self.driver(self.ip)
            except StatementError:
                # Do nothing if database is not responding
                return None