YorkUphysLabV2 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,131 @@
1
+ import serial
2
+ import re
3
+ import serial.tools.list_ports
4
+ import random
5
+ import time
6
+
7
+
8
+ class ScoutSTX:
9
+ def __init__(self, emul=False, keyword='ES', baudrate=9600, timeout=1, port=None) -> None:
10
+ self.port = port
11
+ self.keyword = keyword
12
+ self.timeout = timeout
13
+ self.baudrate = baudrate
14
+ self.inst = None
15
+ self.emul = emul
16
+ self.inst_is_open = False
17
+ if self.emul:
18
+ self.emul_str = "[Emulation mode]:"
19
+ else:
20
+ self.emul_str = ""
21
+
22
+ def connect(self):
23
+ if not self.emul:
24
+ if not self.inst_is_open:
25
+ if self.port:
26
+ self.inst = serial.Serial(self.port, self.baudrate, timeout=self.timeout)
27
+ else:
28
+ self.inst = self.port_search(self.keyword)
29
+
30
+ if self.inst is not None:
31
+ print(f'Connected to ScoutSTX scale.')
32
+ self.inst_is_open = True
33
+ return True
34
+ else:
35
+ print('ScoutSTX Connection failed.')
36
+ return False
37
+ else:
38
+ print('ScoutSTX is already connected.')
39
+ else:
40
+ print(f'{self.emul_str} Connected to ScoutSTX scale.')
41
+ self.inst = 'emulated scale'
42
+ self.inst_is_open = True
43
+ return True
44
+
45
+ def port_search(self, keyword):
46
+ print('Searching for the device...')
47
+ ports = serial.tools.list_ports.comports()
48
+ for port, desc, hwid in sorted(ports):
49
+ ser = serial.Serial(port, self.baudrate, timeout=self.timeout)
50
+ ser.write(b'*IDN?\r\n')
51
+ idn = ser.readline().strip().decode('ascii')
52
+
53
+ if keyword in idn:
54
+ print(f'"{keyword}" found in: {port}')
55
+ return ser
56
+ else:
57
+ ser.close()
58
+
59
+ print(f'"{keyword}" is not found on any port')
60
+ return None
61
+
62
+ def close_connection(self):
63
+ if not self.emul:
64
+ if self.inst is not None and self.inst.is_open:
65
+ self.inst.close()
66
+ print('ScoutSTX Connection closed.')
67
+ else:
68
+ print('No active ScoutSTX connection to close.')
69
+ else:
70
+ if self.inst is not None and self.inst_is_open:
71
+ print(f'{self.emul_str} ScoutSTX Connection closed.')
72
+ self.inst_is_open = False
73
+
74
+
75
+ def is_connected(self):
76
+ if self.inst is None:
77
+ return False
78
+ if self.emul:
79
+ return self.inst_is_open
80
+ else:
81
+ return self.inst.is_open
82
+
83
+ def read_weight(self):
84
+ # Send command to scale to read weight
85
+ if self.is_connected():
86
+ if not self.emul:
87
+ time.sleep(1) # wait for the scale to settle
88
+ self.inst.write(b'S\r\n')
89
+ response = self.inst.readline().decode().strip()
90
+ # Parse weight from response
91
+ if response.startswith('S'):
92
+ match = re.search(r"[-+]?\d*\.\d+|\d+", response)
93
+ if match:
94
+ weight = float(match.group())
95
+ else:
96
+ print("Error parsing weight from scale's response")
97
+ return None
98
+ else:
99
+ print("Error scale's response")
100
+ return None
101
+ else:
102
+ # Generate random weight
103
+ min_value = 1.0
104
+ max_value = 10.0
105
+ weight = round(random.uniform(min_value, max_value), 2)
106
+
107
+ return weight
108
+
109
+ else:
110
+ print(f'{self.emul_str} ScoutSTX Connection is not established.')
111
+ return None
112
+
113
+ #==============================================================================
114
+
115
+ # how to use this class
116
+ if __name__ == "__main__":
117
+
118
+ scale = ScoutSTX(emul=True)
119
+ #scale = ScoutSTX(port='COM7')
120
+
121
+ # connect to the device
122
+ scale.connect()
123
+
124
+ wt = scale.read_weight_time()
125
+ if wt: print(f"Weight: {wt[0]} g, at {wt[1]}")
126
+
127
+ w = scale.read_weight()
128
+ if w: print(f"Weight: {w} g")
129
+
130
+ # close the connection
131
+ scale.close_connection()
File without changes
@@ -0,0 +1,456 @@
1
+ import pyvisa
2
+ import numpy as np
3
+ import logging
4
+
5
+ #----------------------------------------------
6
+ logging.getLogger().setLevel(logging.INFO)
7
+
8
+ class TBS1000:
9
+ """
10
+ Represents a Tektronix TBS1000 oscilloscope.
11
+
12
+ Attributes:
13
+ keyword (str): The keyword used to identify the oscilloscope.
14
+ timeout (int): The timeout value for communication with the oscilloscope.
15
+ encoding (str): The encoding used for communication with the oscilloscope.
16
+ read_termination (str): The read termination character for communication with the oscilloscope.
17
+ write_termination (str): The write termination character for communication with the oscilloscope.
18
+ inst (visa.Resource): The instrument resource representing the connected oscilloscope.
19
+ is_open (bool): Indicates whether the oscilloscope connection is open or closed.
20
+ total_time (flote): total time span of the waveform in seconds
21
+ """
22
+
23
+ def __init__(self, keyword='TBS', timeout=10000, encoding = 'latin_1', read_termination = '\n') -> None:
24
+ self.keyword = keyword
25
+ self.timeout = timeout
26
+ self.encoding = encoding
27
+ self.read_termination = read_termination
28
+ self.write_termination = None
29
+
30
+ self.inst = None
31
+ self.is_open = False
32
+
33
+ self.total_time = 0
34
+ self.record = 0
35
+
36
+
37
+ #----------------------------------------------
38
+ def connect(self):
39
+ rm = pyvisa.ResourceManager()
40
+ resources_list = rm.list_resources()
41
+
42
+ for re in resources_list:
43
+ if 'USB' in re:
44
+ dev = rm.open_resource(re)
45
+ if self.keyword in dev.query('*idn?'):
46
+ self.inst = dev
47
+ break
48
+ else:
49
+ dev.close()
50
+ if self.inst:
51
+ logging.info(f"Tektronix TBS scope found: {self.inst.query('*idn?')}")
52
+ self.is_open = True
53
+ self.inst.timeout = self.timeout # ms
54
+ self.inst.encoding = self.encoding
55
+ self.inst.read_termination = self.read_termination
56
+ self.inst.write_termination = self.write_termination
57
+ self.inst.write('*cls') # clear Event Status Register (ESR)
58
+ #self.config()
59
+ return True
60
+ else:
61
+ logging.info('No Tektronix TBS scope was found!')
62
+ return False
63
+
64
+ #----------------------------------------------
65
+ def config(self, hscale='5E-3', ch1scale='50E-3', ch2scale='2', trig='CH2'):
66
+ """
67
+ Configures the Tektronix TBS oscilloscope with the specified settings.
68
+
69
+ Args:
70
+ hscale (str): Horizontal scale (time/division) setting in seconds. Default is '5E-3'.
71
+ ch1scale (str): CH1 vertical scale (voltage/division) setting in seconds. Default is '50E-3'.
72
+ ch2scale (str): CH2 vertical scale (voltage/division) setting in seconds. Default is '2'.
73
+ trig (str): Trigger source setting. Default is 'CH2'. Valid options are CH1 or CH2
74
+
75
+ Returns:
76
+ bool: True if the oscilloscope is successfully configured, False otherwise.
77
+
78
+ Raises:
79
+ TypeError: If any of the arguments are not of type str.
80
+ """
81
+ if not all(isinstance(arg, str) for arg in (hscale, ch1scale, ch2scale, trig)):
82
+ raise TypeError("All arguments must be of type string.")
83
+
84
+ # Rest of the code...
85
+ if self.is_connected():
86
+ self.inst.write('*rst') # reset the instrument to a known state.
87
+ r = self.inst.query('*opc?') # queries the instrument to check if it has completed the previous operation.
88
+ self.inst.write('autoset EXECUTE') # autoset: automatically adjusts the oscilloscope's settings based on the input signal
89
+ r = self.inst.query('*opc?')
90
+ self.inst.write(f'HORIZONTAL:MAIN:SCALE {hscale}') # set horizontal scale (time/division)
91
+ r = self.inst.query('*opc?')
92
+ self.inst.write('CH1:COUPLING AC')
93
+ r = self.inst.query('*opc?')
94
+ self.inst.write(f'CH1:SCALE {ch1scale}') # set ch1 vertical scale (voltage/division)
95
+ r = self.inst.query('*opc?')
96
+ self.inst.write(f'CH2:SCALE {ch2scale}') # set ch2 vertical scale (voltage/division)
97
+ r = self.inst.query('*opc?')
98
+ self.inst.write(f'TRIGGER:MAIN:EDGE:SOURCE {trig}') # set trigger source to channel 2
99
+ r = self.inst.query('*opc?')
100
+ return True
101
+ else:
102
+ logging.info('Tektronix TBS scope is not connected!')
103
+ return False
104
+
105
+ #----------------------------------------------
106
+ def close(self):
107
+ if self.is_connected():
108
+ self.inst.close()
109
+ self.is_open = False
110
+ logging.info('Tektronix TBS scope connection is closed.')
111
+ else:
112
+ logging.info('Tektronix TBS scope is not connected!')
113
+
114
+ #----------------------------------------------
115
+ def is_connected(self):
116
+ return self.is_open
117
+
118
+ #----------------------------------------------
119
+ def get_idn(self):
120
+ if self.is_connected():
121
+ return self.inst.query('*idn?')
122
+ else:
123
+ return None
124
+
125
+ #----------------------------------------------
126
+ def get_period(self, channel):
127
+ """
128
+ Get the period of the waveform on the specified channel.
129
+
130
+ Args:
131
+ channel (int): The channel number (1 or 2).
132
+ Returns:
133
+ float: The measured period in seconds.
134
+ Raises:
135
+ ValueError: If the channel number is not 1 or 2.
136
+ """
137
+
138
+ if channel not in [1,2]:
139
+ raise ValueError("Channel must be 1 or 2")
140
+
141
+ self.inst.write('MEASUrement:IMMed:TYPE PERiod')
142
+ self.inst.write(f'MEASUrement:IMMed:SOUrce CH{channel}')
143
+ measured_period = self.inst.query('MEASUrement:IMMed:VALue?')
144
+
145
+ return float(measured_period) # in seconds
146
+
147
+ #----------------------------------------------
148
+ def get_frequency(self, channel):
149
+ """
150
+ Get the frequency of the waveform on the specified channel.
151
+
152
+ Args:
153
+ channel (int): The channel number (1 or 2).
154
+ Returns:
155
+ float: The measured frequency in Hz.
156
+ Raises:
157
+ ValueError: If the channel number is not 1 or 2.
158
+ """
159
+
160
+ if channel not in [1,2]:
161
+ raise ValueError("Channel must be 1 or 2")
162
+
163
+ self.inst.write('MEASUrement:IMMed:TYPE FREQUENCY')
164
+ self.inst.write(f'MEASUrement:IMMed:SOUrce CH{channel}')
165
+ measured_freq = self.inst.query('MEASUrement:IMMed:VALue?')
166
+
167
+ return float(measured_freq) # in seconds
168
+
169
+ def get_amplitude(self, channel):
170
+ """
171
+ Get the amplitude of the waveform on the specified channel.
172
+
173
+ Args:
174
+ channel (int): The channel number (1 or 2).
175
+ Returns:
176
+ float: The measured amplitude in V.
177
+ Raises:
178
+ ValueError: If the channel number is not 1 or 2.
179
+ """
180
+
181
+ if channel not in [1,2]:
182
+ raise ValueError("Channel must be 1 or 2")
183
+
184
+ self.inst.write('MEASUrement:IMMed:TYPE AMplitude')
185
+ self.inst.write(f'MEASUrement:IMMed:SOUrce CH{channel}')
186
+ measured_amplitude = self.inst.query('MEASUrement:IMMed:VALue?')
187
+
188
+ return float(measured_amplitude) # in seconds
189
+
190
+ def get_phase(self, channel):
191
+ """
192
+ Get the phase difference from the selected waveform to the designated waveform
193
+
194
+ Args:
195
+ channel (int): The channel number (1 or 2).
196
+ Returns:
197
+ float: The measured phase in degrees.
198
+ Raises:
199
+ ValueError: If the channel number is not 1 or 2.
200
+ """
201
+
202
+ if channel not in [1,2]:
203
+ raise ValueError("Channel must be 1 or 2")
204
+
205
+ self.inst.write(f'MEASUrement:IMMed:SOUrce1 CH1')
206
+ self.inst.write(f'MEASUrement:IMMed:SOUrce2 CH2')
207
+ self.inst.write('MEASUrement:IMMed:TYPE PHAse')
208
+ measured_phase = self.inst.query('MEASUrement:IMMed:VALue?')
209
+
210
+ if channel == 1:
211
+ return float(measured_phase) # in degrees
212
+ else:
213
+ return -1*float(measured_phase) # in degrees
214
+ #----------------------------------------------
215
+ def get_data(self, channel):
216
+ """
217
+ Retrieves the waveform data from the oscilloscope for the specified channel.
218
+
219
+ Args:
220
+ channel (int): The channel number (1 or 2) for which to retrieve the data.
221
+
222
+ Returns:
223
+ A two-dimentional list(array) of the waveform containing the following elements:
224
+ waveform[0]: An array of scaled time values in milliseconds, i.e., the x-axis values
225
+ waveform[1]: An array of scaled amplitude values in volts, i.e., the y-axis values
226
+ """
227
+ if not self.is_connected():
228
+ return None
229
+ if channel not in [1, 2]:
230
+ raise ValueError("Channel must be 1 or 2")
231
+
232
+ # io config
233
+ self.inst.write('header 0')
234
+ self.inst.write('data:encdg RIBINARY')
235
+ self.inst.write(f'data:source CH{channel}')
236
+ self.inst.write('data:start 1') # first sample
237
+ self.record = int(self.inst.query('wfmpre:nr_pt?')) # number of samples
238
+ self.inst.write(f'data:stop {self.record}') # last sample
239
+ self.inst.write('wfmpre:byt_nr 1') # 1 byte per sample
240
+ # acq config
241
+ self.inst.write('acquire:state 0') # stop data acquisition
242
+ self.inst.write('acquire:stopafter SEQUENCE') # sets the acquisition mode to 'SEQUENCE': acquires a single waveform and then stops
243
+ self.inst.write('acquire:state 1') # run
244
+
245
+ # data query
246
+ bin_wave = self.inst.query_binary_values('curve?', datatype='b', container=np.array)
247
+ tscale = float(self.inst.query('wfmpre:xincr?')) # retrieve scaling factors
248
+ tstart = float(self.inst.query('wfmpre:xzero?'))
249
+ vscale = float(self.inst.query('wfmpre:ymult?')) # volts / level
250
+ voff = float(self.inst.query('wfmpre:yzero?')) # reference voltage
251
+ vpos = float(self.inst.query('wfmpre:yoff?')) # reference position (level)
252
+
253
+ # error checking
254
+ r = int(self.inst.query('*esr?'))
255
+ if r != 0b00000000:
256
+ logging.info('event status register: 0b{:08b}'.format(r))
257
+ r = self.inst.query('allev?').strip()
258
+ if 'No events' not in r:
259
+ logging.info(f'all event messages: {r}')
260
+
261
+ total_time = tscale * self.record # create scaled vectors
262
+ tstop = tstart + total_time
263
+ scaled_time = np.linspace(tstart, tstop, num=self.record, endpoint=False) * 1000 # time in ms
264
+
265
+ unscaled_amp = np.array(bin_wave, dtype='double') # data type conversion
266
+ scaled_amp = (unscaled_amp - vpos) * vscale + voff
267
+
268
+ self.total_time = total_time
269
+
270
+ waveform = np.zeros((2,len(scaled_amp)))
271
+ waveform[0] = scaled_time
272
+ waveform[1] = scaled_amp
273
+
274
+ return waveform
275
+
276
+ #----------------------------------------------
277
+ def get_data2(self):
278
+ """
279
+ Retrieves waveforms data of both channels of the Tektronix TBS1000 oscilloscope, simultaneously.
280
+
281
+ Returns:
282
+ A two-dimentional list(array) of the waveform, for each channel, containing the following elements:
283
+ waveform[0]: An array of scaled time values in milliseconds, i.e., the x-axis values
284
+ waveform[1]: An array of scaled amplitude values in volts, i.e., the y-axis values
285
+
286
+ the returned values are in the following order: (waveform_1, waveform_2)
287
+ """
288
+ if not self.is_connected():
289
+ return None
290
+
291
+ scaled_amp = []
292
+
293
+ # acq config
294
+ self.inst.write('acquire:state RUN') # RUN acquisition
295
+ self.inst.write('acquire:stopafter SEQUENCE') # sets the acquisition mode to 'SEQUENCE': acquires a single waveform and then stops
296
+ self.inst.write('acquire:state 0') # stop data acquisition
297
+
298
+ # io config
299
+ self.inst.write('header 0')
300
+ self.inst.write('data:encdg RIBINARY')
301
+
302
+ for channel in [1,2]:
303
+ # ch1
304
+ self.inst.write(f'data:source CH{channel}')
305
+ self.inst.write('data:start 1') # first sample
306
+ self.record = int(self.inst.query('wfmpre:nr_pt?')) # number of samples
307
+ self.inst.write(f'data:stop {self.record}') # last sample
308
+ self.inst.write('wfmpre:byt_nr 1') # 1 byte per sample
309
+
310
+ # data query
311
+ bin_wave = self.inst.query_binary_values('curve?', datatype='b', container=np.array)
312
+ tscale = float(self.inst.query('wfmpre:xincr?')) # retrieve scaling factors
313
+ tstart = float(self.inst.query('wfmpre:xzero?'))
314
+ vscale = float(self.inst.query('wfmpre:ymult?')) # volts / level
315
+ voff = float(self.inst.query('wfmpre:yzero?')) # reference voltage
316
+ vpos = float(self.inst.query('wfmpre:yoff?')) # reference position (level)
317
+
318
+ # error checking
319
+ r = int(self.inst.query('*esr?'))
320
+ if r != 0b00000000:
321
+ logging.info('event status register: 0b{:08b}'.format(r))
322
+ r = self.inst.query('allev?').strip()
323
+ if 'No events' not in r:
324
+ logging.info(f'all event messages: {r}')
325
+
326
+ total_time = tscale * self.record # create scaled vectors
327
+ tstop = tstart + total_time
328
+ scaled_time = np.linspace(tstart, tstop, num=self.record, endpoint=False) * 1000 # time in ms
329
+
330
+ unscaled_amp = np.array(bin_wave, dtype='double') # data type conversion
331
+ scaled_amp.append((unscaled_amp - vpos) * vscale + voff)
332
+
333
+ self.total_time = total_time
334
+
335
+ waveform_1 = np.zeros((2,len(scaled_amp[0])))
336
+ waveform_2 = np.zeros((2,len(scaled_amp[1])))
337
+
338
+
339
+ waveform_1[0] = scaled_time
340
+ waveform_1[1] = scaled_amp[0]
341
+ waveform_2[0] = scaled_time
342
+ waveform_2[1] = scaled_amp[1]
343
+
344
+ self.inst.write('acquire:state RUN') # RUN acquisition
345
+
346
+ return waveform_1, waveform_2
347
+ #----------------------------------------------
348
+ def shift_phase(self, scaled_time, waveform_1, waveform_2, phi):
349
+ """
350
+ Apply phase shift to waveform_2 based on the given phi value. To keep the length of the waveforms the same, the shifted waveforms and scaled_time are truncated.
351
+
352
+ Parameters:
353
+ scaled_time (array-like): Array of scaled time values.
354
+ waveform_1 (array-like): Array of waveform 1 values.
355
+ waveform_2 (array-like): Array of waveform 2 values.
356
+ phi (float): Phase shift value in degrees.
357
+
358
+ Returns:
359
+ tuple: A tuple containing the shifted scaled_time, waveform_1, and shifted waveform_2.
360
+ """
361
+
362
+ _, phase = divmod(phi, 360)
363
+ # get the period of waveform 2
364
+ period = self.get_period(channel=2)
365
+ samples_in_period = int(period/self.total_time * len(waveform_2)) # number of samples in one period
366
+ shift_samples = int((phase / 360) * samples_in_period) # number of samples to shift
367
+
368
+ if shift_samples == 0:
369
+ return scaled_time, waveform_1, waveform_2
370
+ else:
371
+ return scaled_time[:-1*shift_samples], waveform_1[:-1*shift_samples], waveform_2[shift_samples:]
372
+
373
+ #----------------------------------------------
374
+ def shift_phase2(self, ref_waveform, ref_channel, phi_shift):
375
+ """
376
+ Constructing an internal reference waveform with the given phase shift w.r.t the main waveform based on the given parameters.
377
+
378
+ Parameters:
379
+ ref_waveform (float): The oscilloscope's readout of the rwaveform (2-dimentional list) of the reference channel
380
+ ref_channel (str): The reference channel for obtaining the frequency
381
+ phi_shift (float): The phase shift value in degrees.
382
+
383
+ Returns:
384
+ A two-dimentional list(array), with the first row element the time values and the second element being the constructed amplitude values with the shifted phase.
385
+ """
386
+ internal_ref_waveform = np.zeros((2,len(ref_waveform)))
387
+ t = ref_waveform[0]
388
+ freq = self.get_frequency(ref_channel)
389
+ ampl = self.get_amplitude(ref_channel)
390
+
391
+ # constructing a pure analytical internal ref. wave
392
+ internal_ref_waveform[0] = t
393
+ internal_ref_waveform[1] = ampl*np.cos(2*np.pi*freq*t*0.001 + np.radians(phi_shift))
394
+
395
+ return internal_ref_waveform
396
+
397
+ #==============================================================================
398
+ # how to use this class
399
+ if __name__ == '__main__':
400
+ # create a scope object
401
+ scope = TBS1000()
402
+ # connect to the scope
403
+ if scope.connect():
404
+ scope.config(hscale='5E-3', ch1scale='50E-3', ch2scale='2', trig='CH2')
405
+
406
+ color = {1:'orange', 2:'blue', 'mix':'red'}
407
+
408
+ #"""
409
+ try:
410
+ # get data from the scope
411
+ scaled_time, scaled_wave_1, scaled_wave_2 = scope.get_data2()
412
+
413
+ phi = 0
414
+ #stime, wf1, wf2_shifted = scope.shift_phase(scaled_time, scaled_wave_1, scaled_wave_2, phi)
415
+ wf2_shifted = scope.shift_phase2(scaled_time, ref_channel=2, phi_shift = 330)
416
+
417
+ mix_wf = [x*y for x,y in zip(scaled_wave_1,wf2_shifted)]
418
+
419
+ avg_mix_wf = np.average(mix_wf)*1000 # in mV
420
+
421
+ # --plotting
422
+ #'''
423
+ import pylab as pl
424
+ #pl.plot(scaled_time, scaled_wave_1, label=f'Ch 1', color=color[1])
425
+ #y_max = max(scaled_wave_1)
426
+
427
+ pl.plot(scaled_time, scaled_wave_1, label=f'Ch 1', color=color[1])
428
+ pl.plot(scaled_time, wf2_shifted, label=f'Ch 2', color=color[2])
429
+ #pl.plot(stime, mix_wf, label=f'Mix', color=color['mix'])
430
+
431
+ y_max = max(max(scaled_wave_1), max(wf2_shifted))
432
+ #y_max = max(max(wf1), max(wf2_shifted), max(mix_wf))
433
+
434
+ pl.ylim(top=y_max*1.5)
435
+ pl.xlabel('time [ms]') # x label
436
+ pl.ylabel('voltage [v]') # y label
437
+ # Add legend
438
+ pl.legend(loc='upper right')
439
+
440
+ pl.rc('grid', linestyle=':', color='gray', linewidth=1)
441
+ pl.grid(True)
442
+ pl.title(f'Lock-in Output: {round(avg_mix_wf,2)} mV, $\Delta\phi: {phi}\degree$', fontsize = 10)
443
+ #'''
444
+
445
+ except ValueError as e:
446
+ print(e)
447
+
448
+
449
+ scope.close()
450
+
451
+ print("\nlook for plot window...")
452
+ pl.show()
453
+ print("\nend of demonstration")
454
+
455
+
456
+ scope.close()
File without changes
@@ -0,0 +1,56 @@
1
+ import os
2
+ import csv
3
+ import logging
4
+
5
+ #from YorkUphysLab.Actuator import Actuator as ACT
6
+ #from YorkUphysLab.HVcontrol import HV_control as HV
7
+ #----------------------------------------------
8
+
9
+ logging.getLogger().setLevel(logging.INFO)
10
+
11
+ def write_data_to_csv(data, path, filename):
12
+ header = ['Position', 'Weight']
13
+
14
+ # Create the full path for the CSV file on the desktop
15
+ file_path = os.path.join(path, filename)
16
+
17
+ try:
18
+ with open(file_path, 'w', newline='') as csvfile:
19
+ csv_writer = csv.writer(csvfile)
20
+ csv_writer.writerow(header)
21
+
22
+ for position, weight in data:
23
+ csv_writer.writerow([position, weight])
24
+ print(f"Data written to '{file_path}' successfully!")
25
+ return True
26
+ except Exception as e:
27
+ print(f"An error occurred: {e}")
28
+ return False
29
+
30
+ #----------------------------------------------
31
+ def reset_devices(actuator, hv):
32
+ if not actuator.actuator_on:
33
+ actuator.switch_on()
34
+ actuator.set_position(0)
35
+
36
+ if not hv.HV_on:
37
+ hv.switch_on()
38
+ hv.set_hv(0)
39
+
40
+ #----------------------------------------------
41
+ def multiply_lists(list_1, list_2):
42
+ """
43
+ Multiplies (mixes) two waveforms element-wise.
44
+
45
+ Args:
46
+ list_1 (list)
47
+ list_2 (list)
48
+
49
+ Returns:
50
+ list: The resulting list after element-wise multiplication.
51
+ """
52
+ if len(list_1) != len(list_2):
53
+ logging.error(f"Lists must be the same length: {len(list_1)} != {len(list_2)}")
54
+ return None
55
+
56
+ return [x*y for x,y in zip(list_1,list_2)]
File without changes
@@ -0,0 +1 @@
1
+ from . __version__ import __version__
@@ -0,0 +1 @@
1
+ __version__ = '1.0.27'