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,133 @@
1
+ import nidaqmx
2
+ from YorkUphysLab.GwINSTEK import GPD3303D as PSU
3
+ import time
4
+
5
+
6
+ class Actuator:
7
+ def __init__(self, _DAQ_mame, _psu, emul=False, _max_time= 12) -> None:
8
+ self.sdaq = _DAQ_mame
9
+ self.psu = _psu
10
+ self.max_time = _max_time
11
+ self.max_pos = 100 # mm
12
+ self.actuator_on = False
13
+ self.emul = emul
14
+ self.current_pos = 0
15
+ if self.emul:
16
+ self.emul_str = "[Emulation mode]:"
17
+ else:
18
+ self.emul_str = ""
19
+
20
+
21
+ def set_position(self, pos):
22
+ if not self.actuator_on:
23
+ print(f'{self.emul_str} Actuator is switched OFF. Switch it ON first!')
24
+ return None
25
+
26
+ # when PSU connection is closed by another device:
27
+ if not self.psu.is_connected():
28
+ print("PSU Connection is not established. Switching off other devise sharing same PSU might have caused this. Switch on this decive and try again.")
29
+ return None
30
+
31
+
32
+ if 0 <= pos <= self.max_pos:
33
+ Vctrl = -0.00004*pos*pos + 0.0528*pos + 0.1
34
+ else:
35
+ print(f'{self.emul_str} position ({pos} mm) out of range. use 0-{self.max_pos} mm')
36
+ return None
37
+
38
+ #if self.actuator_on:
39
+ self.current_pos = self.get_position()
40
+ required_time = abs(pos - self.current_pos)*self.max_time/self.max_pos + 1
41
+
42
+ if not self.emul:
43
+ with nidaqmx.Task() as task:
44
+ task.ao_channels.add_ao_voltage_chan(f'{self.sdaq}/ao0','mychannel',0,5)
45
+ task.write(Vctrl)
46
+ task.stop()
47
+
48
+ time.sleep(required_time)
49
+ print(f'{self.emul_str} position set to {pos} mm')
50
+ self.current_pos = pos
51
+
52
+ def get_position(self):
53
+ if not self.actuator_on:
54
+ print(f'{self.emul_str} Actuator is switched OFF! Switch it ON first!')
55
+ return None
56
+
57
+ # when PSU connection is closed by another device:
58
+ if not self.psu.is_connected():
59
+ print("PSU Connection is not established. Switching off other devise sharing same PSU might have caused this. Switch on this decive and try again.")
60
+ return None
61
+
62
+ #if self.actuator_on:
63
+ if not self.emul:
64
+ with nidaqmx.Task() as task:
65
+ task.ai_channels.add_ai_voltage_chan(f'{self.sdaq}/ai0')
66
+ Vadc = task.read()
67
+ pos = 55.33223 - 31.31889*Vadc + 0.5730428*Vadc*Vadc
68
+ else:
69
+ pos = self.current_pos
70
+
71
+ return round(pos,1)
72
+
73
+ def switch_on(self):
74
+ try:
75
+ if not self.psu.is_connected():
76
+ self.psu.connect()
77
+
78
+ if self.psu.is_connected():
79
+ self.psu.set_voltage(1, 12)
80
+ self.psu.set_current(1, 0.5)
81
+ self.psu.enable_output()
82
+ self.actuator_on = True
83
+ message = f'{self.emul_str} Actuator switched ON.'
84
+ else:
85
+ message = f'{self.emul_str} PSU Connection is not established.'
86
+
87
+ except Exception as e:
88
+ message = f'{self.emul_str} An error occurred: {str(e)}'
89
+
90
+ print(message)
91
+
92
+ return self.actuator_on
93
+
94
+ def switch_off(self):
95
+ self.psu.disable_output()
96
+ #self.psu.close_connection()
97
+ print(f'{self.emul_str} Actuator switched OFF.')
98
+ self.actuator_on = False
99
+ return True
100
+ #==============================================================================
101
+
102
+ # how to use this class
103
+ if __name__ == "__main__":
104
+
105
+ DAQ_mame = 'SDAQ-25'
106
+ # create power-supply and actuator objects
107
+ psu = PSU.GPD3303D(emul=False)
108
+ actuator = Actuator(DAQ_mame, psu, emul=False)
109
+
110
+ #psu.connect()
111
+ actuator.switch_on()
112
+ actuator.set_position(100)
113
+ print(f'position moved to = {actuator.get_position()} mm')
114
+
115
+ time.sleep(2)
116
+
117
+ #actuator.switch_off()
118
+
119
+ '''
120
+ SenorDAQ terminals:
121
+ 1: p0.0
122
+ 2: p0.1
123
+ 3: p0.2
124
+ 4: p0.3
125
+ 5: GND
126
+ 6: +5V
127
+ 7: PFIO
128
+ 8: GND
129
+ 9: AO0 ---->
130
+ 10: GND
131
+ 11: AI0 ---->
132
+ 12: AI1
133
+ '''
File without changes
@@ -0,0 +1,164 @@
1
+ import serial
2
+ import time
3
+ import serial.tools.list_ports
4
+ import logging
5
+
6
+ #----------------------------------------------
7
+ logging.getLogger().setLevel(logging.INFO)
8
+
9
+ class AFG2000:
10
+
11
+ def __init__(self, emul=False, keyword='AFG', baudrate=9600, timeout=1, port=None) -> None:
12
+ self.port = port
13
+ self.keyword = keyword
14
+ self.timeout = timeout
15
+ self.baudrate = baudrate
16
+ self.inst = None
17
+ #---------------------------------------------------
18
+
19
+ def connect(self):
20
+ if self.inst is not None and self.inst.is_open:
21
+ logging.info('AFG Connection is already established.')
22
+ return True
23
+ if self.port:
24
+ self.inst = serial.Serial(self.port, self.baudrate, timeout=self.timeout)
25
+ else:
26
+ self.inst = self.port_search(self.keyword)
27
+
28
+ if self.inst is not None:
29
+ logging.info(f'Connected to {self.get_idn()}')
30
+ return True
31
+ else:
32
+ logging.error('AFG Connection failed.')
33
+ return False
34
+ #---------------------------------------------------
35
+
36
+ def port_search(self, keyword):
37
+ logging.info('Searching for the device...')
38
+ ports = serial.tools.list_ports.comports()
39
+ for port, desc, hwid in sorted(ports):
40
+ ser = serial.Serial(port, self.baudrate, timeout=self.timeout)
41
+ ser.write(b'*IDN?\r\n')
42
+ idn = ser.readline().strip().decode('ascii')
43
+
44
+ if keyword in idn:
45
+ logging.info(f'"{keyword}" found in: {port}')
46
+ return ser
47
+ else:
48
+ ser.close()
49
+
50
+ logging.info(f'"{keyword}" is not found on any port')
51
+ return None
52
+ #---------------------------------------------------
53
+
54
+ def close(self):
55
+ if self.inst is not None and self.inst.is_open:
56
+ self.inst.close()
57
+ logging.info('AFG Connection closed.')
58
+ else:
59
+ logging.info('No active AFG connection to close.')
60
+ #---------------------------------------------------
61
+
62
+ def is_connected(self):
63
+ if self.inst is None:
64
+ return False
65
+ return self.inst.is_open
66
+ #---------------------------------------------------
67
+
68
+ def send_cmd(self, cmd):
69
+ if self.is_connected():
70
+ self.inst.write(cmd.encode('ascii') + b'\r\n')
71
+ resp = self.inst.readline().strip().decode('ascii')
72
+ return resp
73
+ else:
74
+ logging.info('AFG Connection is not established.')
75
+ return None
76
+ #---------------------------------------------------
77
+
78
+ def get_idn(self):
79
+ return self.send_cmd('*IDN?')
80
+ #---------------------------------------------------
81
+
82
+ def set_frequency(self, freq):
83
+ """
84
+ Sets the frequency of the AFG2000 function generator.
85
+
86
+ Parameters:
87
+ freq (float): The desired frequency in Hz.
88
+
89
+ Returns:
90
+ bool: True if the frequency is set successfully, False otherwise.
91
+ """
92
+ if self.is_connected():
93
+ self.send_cmd(f'SOUR1:FREQ {freq}')
94
+ return True
95
+ else:
96
+ logging.info('AFG2000 function generator is not connected!')
97
+ return False
98
+ #---------------------------------------------------
99
+
100
+ def set_waveform(self, waveform='SIN'):
101
+ if self.is_connected():
102
+ if waveform in ['SIN', 'SQU', 'RAMP', 'NOIS', 'ARB']:
103
+ self.send_cmd(f'SOUR1:FUNC {waveform}')
104
+ return True
105
+ else:
106
+ logging.error(f'Invalid waveform: {waveform}. Valid waveforms are "SIN", "SQU", "RAMP", "NOIS", and "ARB".')
107
+ return None
108
+ else:
109
+ logging.info('AFG2000 function generator is not connected!')
110
+ return False
111
+ #---------------------------------------------------
112
+
113
+ def set_amplitude(self, amplitude, unit='VPP'):
114
+ if self.is_connected():
115
+ if unit in ['VPP', 'VRMS', 'DBM']:
116
+ self.send_cmd(f'SOUR1:VOLT:UNIT {unit}')
117
+ else:
118
+ logging.error(f'Invalid unit: {unit}. Valid units are "VPP", "VRMS", and "DBM".')
119
+ return None
120
+ self.send_cmd(f'SOUR1:AMPL {amplitude}')
121
+ return True
122
+ else:
123
+ logging.info('AFG2000 function generator is not connected!')
124
+ return False
125
+ #---------------------------------------------------
126
+
127
+ def set_DCoffset(self, offset):
128
+ if self.is_connected():
129
+ self.send_cmd(f'SOUR1:DCO {offset}')
130
+ return True
131
+ else:
132
+ logging.info('AFG2000 function generator is not connected!')
133
+ return False
134
+ #---------------------------------------------------
135
+
136
+ def set_output(self, state):
137
+ if self.is_connected():
138
+ if state in ['ON', 'OFF']:
139
+ self.send_cmd(f'OUTP {state}')
140
+ return True
141
+ else:
142
+ logging.error(f'Invalid output state: {state}. Valid states are "ON" and "OFF".')
143
+ return None
144
+ else:
145
+ logging.info('AFG2000 function generator is not connected!')
146
+ return False
147
+ #==============================================================================
148
+
149
+ # how to use this class
150
+ if __name__ == '__main__':
151
+ afg = AFG2000()
152
+ afg.connect()
153
+ afg.set_waveform('SIN')
154
+ afg.set_frequency('70')
155
+ afg.set_amplitude('0.2', 'VPP')
156
+ afg.set_DCoffset('0.0')
157
+ afg.set_output('ON')
158
+ time.sleep(10)
159
+
160
+ afg.set_amplitude('0.35')
161
+ afg.set_frequency('90')
162
+ time.sleep(10)
163
+ afg.set_output('OFF')
164
+ afg.close()
@@ -0,0 +1,218 @@
1
+ import serial
2
+ import time
3
+ import serial.tools.list_ports
4
+
5
+ class GPD3303D:
6
+ """
7
+ Represents the GPD3303D Programmable Power Supply Unit (PSU).
8
+
9
+ Args:
10
+ emul (bool): Whether to enable emulation mode. Default is False.
11
+ keyword (str): The keyword to search for in the device identification string. Default is 'GPD'.
12
+ baudrate (int): The baud rate for serial communication. Default is 9600.
13
+ timeout (float): The timeout duration for serial communication in seconds. Default is 1.
14
+ port (str): The specific port to connect to. If not provided, the port will be searched automatically.
15
+
16
+ Attributes:
17
+ port (str): The port used for communication.
18
+ keyword (str): The keyword used for device identification.
19
+ timeout (float): The timeout duration for serial communication.
20
+ baudrate (int): The baud rate for serial communication.
21
+ inst (serial.Serial or str): The serial connection instance or a string indicating emulation mode.
22
+ emul (bool): Whether emulation mode is enabled.
23
+ inst_is_open (bool): Whether the serial connection is open.
24
+ voltage (dict): A dictionary to store the voltage values for each channel.
25
+ current (dict): A dictionary to store the current values for each channel.
26
+ emul_str (str): A string indicating the emulation mode.
27
+
28
+ Methods:
29
+ connect(): Establishes a connection to the PSU.
30
+ port_search(keyword): Searches for the PSU on available ports.
31
+ send_cmd(cmd): Sends a command to the PSU and returns the response.
32
+ get_idn(): Retrieves the identification string of the PSU.
33
+ set_voltage(channel, voltage): Sets the voltage for a specific channel.
34
+ set_current(channel, current): Sets the current for a specific channel.
35
+ enable_output(): Enables the output of the PSU.
36
+ disable_output(): Disables the output of the PSU.
37
+ enable_beep(): Enables the beep sound of the PSU.
38
+ disable_beep(): Disables the beep sound of the PSU.
39
+ get_voltage(channel): Retrieves the voltage value for a specific channel.
40
+ get_current(channel): Retrieves the current value for a specific channel.
41
+ close_connection(): Closes the connection to the PSU.
42
+ is_connected(): Checks if the connection to the PSU is established.
43
+ """
44
+ def __init__(self, emul=False, keyword='GPD', baudrate=9600, timeout=1, port=None) -> None:
45
+ self.port = port
46
+ self.keyword = keyword
47
+ self.timeout = timeout
48
+ self.baudrate = baudrate
49
+ self.inst = None
50
+ self.emul = emul
51
+ self.inst_is_open = False
52
+ self.voltage ={}
53
+ self.current ={}
54
+ if self.emul:
55
+ self.emul_str = "[Emulation mode]:"
56
+ else:
57
+ self.emul_str = ""
58
+
59
+ def connect(self):
60
+ if not self.emul:
61
+ if self.port:
62
+ self.inst = serial.Serial(self.port, self.baudrate, timeout=self.timeout)
63
+ else:
64
+ self.inst = self.port_search(self.keyword)
65
+
66
+ if self.inst is not None:
67
+ print(f'Connected to {self.get_idn()}')
68
+ return True
69
+ else:
70
+ print('PSU Connection failed.')
71
+ return False
72
+ else:
73
+ print(f'{self.emul_str} Connected to GPD3303D PSU.')
74
+ self.inst = 'emulated psu'
75
+ self.inst_is_open = True
76
+ return True
77
+
78
+
79
+ def port_search(self, keyword):
80
+ print('Searching for the device...')
81
+ ports = serial.tools.list_ports.comports()
82
+ for port, desc, hwid in sorted(ports):
83
+ ser = serial.Serial(port, self.baudrate, timeout=self.timeout)
84
+ ser.write(b'*IDN?\r\n')
85
+ idn = ser.readline().strip().decode('ascii')
86
+
87
+ if keyword in idn:
88
+ print(f'"{keyword}" found in: {port}')
89
+ return ser
90
+ else:
91
+ ser.close()
92
+
93
+ print(f'"{keyword}" is not found on any port')
94
+ return None
95
+
96
+
97
+ def send_cmd(self, cmd):
98
+ if self.is_connected():
99
+ self.inst.write(cmd.encode('ascii') + b'\r\n')
100
+ resp = self.inst.readline().strip().decode('ascii')
101
+ return resp
102
+ else:
103
+ print('PSU Connection is not established.')
104
+ return None
105
+
106
+
107
+ def get_idn(self):
108
+ if self.emul:
109
+ return 'Emulated GPD3303D PSU'
110
+ else:
111
+ return self.send_cmd('*IDN?')
112
+
113
+ def set_voltage(self, channel, voltage):
114
+ if not self.emul:
115
+ cmd = f'VSET{channel}:{voltage:.1f}'
116
+ return self.send_cmd(cmd)
117
+ else:
118
+ self.voltage[channel] = voltage
119
+ return True
120
+
121
+ def set_current(self, channel, current):
122
+ if not self.emul:
123
+ cmd = f'ISET{channel}:{current:.2f}'
124
+ return self.send_cmd(cmd)
125
+ else:
126
+ self.current[channel] = current
127
+ return True
128
+
129
+ def enable_output(self):
130
+ if not self.emul: return self.send_cmd('OUT1')
131
+ else:
132
+ print(f'{self.emul_str} PSU output enabled.')
133
+ return True
134
+
135
+
136
+ def disable_output(self):
137
+ if not self.emul: return self.send_cmd('OUT0')
138
+ else:
139
+ print(f'{self.emul_str} PSU output disabled.')
140
+ return True
141
+
142
+ def enable_beep(self):
143
+ if not self.emul: return self.send_cmd('BEEP1')
144
+
145
+ def disable_beep(self):
146
+ if not self.emul: return self.send_cmd('BEEP0')
147
+
148
+ def get_voltage(self, channel):
149
+ if not self.emul:
150
+ response = self.send_cmd(f'VOUT{channel}?')
151
+ if response is not None:
152
+ voltage_str, unit_str = response.strip().split("V")
153
+ return float(voltage_str)
154
+ else:
155
+ return float(self.voltage[channel])
156
+
157
+
158
+ def get_current(self, channel):
159
+ if not self.emul:
160
+ response = self.send_cmd(f'IOUT{channel}?')
161
+ if response is not None:
162
+ current_str, unit_str = response.strip().split("A")
163
+ return float(current_str)
164
+ else:
165
+ return float(self.current[channel])
166
+
167
+ def close_connection(self):
168
+ if not self.emul:
169
+ if self.inst is not None and self.inst.is_open:
170
+ self.inst.close()
171
+ print('PSU Connection closed.')
172
+ else:
173
+ print('No active PSU connection to close.')
174
+ else:
175
+ if self.inst is not None and self.inst_is_open:
176
+ self.inst_is_open = False
177
+ print(f'{self.emul_str} PSU Connection closed.')
178
+ else:
179
+ print(f'{self.emul_str} No active PSU connection to close.')
180
+
181
+ def is_connected(self):
182
+ if self.inst is None:
183
+ return False
184
+ if not self.emul: return self.inst.is_open
185
+ else: return self.inst_is_open
186
+ #==============================================================================
187
+
188
+ # how to use this class
189
+ if __name__ == '__main__':
190
+ # create a power-supply object
191
+ psu = GPD3303D(emul=True)
192
+ #psu = GPD3303D(port='COM8')
193
+
194
+ # connect to the device
195
+ psu.connect()
196
+
197
+ # Set the output voltage and current for channel 1
198
+ psu.set_voltage(1, 12)
199
+ psu.set_current(1, 0.01)
200
+
201
+ # Set the output voltage and current for channel 2
202
+ psu.set_voltage(2, 4.7)
203
+ psu.set_current(2, 0.05)
204
+
205
+ # Enable the output
206
+ psu.enable_output()
207
+
208
+ time.sleep(2)
209
+ #print(f'voltage - CH1: {psu.get_voltage(1)} V')
210
+ #print(f'current - CH1: {psu.get_current(1)} A')
211
+ print(f'voltage - CH2: {psu.get_voltage(2)} V')
212
+ #print(f'current - CH2: {psu.get_current(2)} A')
213
+
214
+ # Disable the output
215
+ psu.disable_output()
216
+
217
+ # close the connection
218
+ psu.close_connection()
File without changes
@@ -0,0 +1,133 @@
1
+ import YorkUphysLab.GwINSTEK.GPD3303D as PSU
2
+ import time
3
+
4
+
5
+ class HV_control:
6
+ def __init__(self, _psu, emul=False) -> None:
7
+ self.psu = _psu
8
+ self.HV_on = False
9
+ self.emul = emul
10
+ if self.emul:
11
+ self.emul_str = "[Emulation mode]:"
12
+ else:
13
+ self.emul_str = ""
14
+
15
+ def switch_on(self):
16
+ try:
17
+ if not self.psu.is_connected():
18
+ self.psu.connect()
19
+
20
+ if self.psu.is_connected():
21
+ self.psu.set_voltage(1, 12)
22
+ self.psu.set_current(1, 0.5)
23
+ self.psu.set_voltage(2, 1)
24
+ self.psu.set_current(2, 0.01)
25
+ self.psu.enable_output()
26
+ self.HV_on = True
27
+ message = f'{self.emul_str} HV switched ON.'
28
+ else:
29
+ message = f'{self.emul_str} PSU Connection is not established.'
30
+
31
+ except Exception as e:
32
+ message = f'{self.emul_str} An error occurred: {str(e)}'
33
+
34
+ print(message)
35
+ return self.HV_on
36
+
37
+ def switch_off(self):
38
+ self.psu.disable_output()
39
+ #self.psu.close_connection()
40
+ print(f'{self.emul_str} HV switched OFF.')
41
+ self.HV_on = False
42
+ return True
43
+
44
+ def set_hv(self, voltage):
45
+ if not self.HV_on:
46
+ print(f'{self.emul_str} HV is switched OFF! Switch it ON first!')
47
+ return None
48
+
49
+ # when PSU connection is closed by another device:
50
+ if not self.psu.is_connected():
51
+ print("PSU Connection is not established. Switching off other devise sharing same PSU might have caused this. Switch on this decive and try again.")
52
+ return None
53
+
54
+ if 0 <= voltage <= 3:
55
+ # Vctrl equation is a linear fit of the data below
56
+ Vctrl = 1.6489*voltage + 0.0595
57
+ Vctrl = 5 if Vctrl >= 5 else round(Vctrl, 1)
58
+
59
+ #if self.HV_on:
60
+ self.psu.set_voltage(2, Vctrl)
61
+ print(f'{self.emul_str} HV voltage set to {voltage} kV')
62
+ return True
63
+
64
+ else:
65
+ print(f'{self.emul_str} Requested High voltage out of range. Use 0 -3 kV')
66
+ return None
67
+
68
+ def get_hv(self):
69
+
70
+ if not self.HV_on:
71
+ print(f'{self.emul_str} HV is switched OFF! Switch it ON first!')
72
+ return None
73
+
74
+ # when PSU connection is closed by another device:
75
+ if not self.psu.is_connected():
76
+ print("PSU Connection is not established. Switching off other devise sharing same PSU might have caused this. Switch on this decive and try again.")
77
+ return None
78
+
79
+ # the output must be enabled to read the voltage
80
+ actual_voltage = self.psu.get_voltage(2)
81
+ #print(f'{self.emul_str} actual_voltage = {actual_voltage} V')
82
+ actual_HV = (actual_voltage - 0.0595)/1.6489
83
+ return round(actual_HV, 2)
84
+
85
+ #==============================================================================
86
+
87
+ # how to use this class
88
+ if __name__ == "__main__":
89
+
90
+ try:
91
+ #psu = PSU.GPD3303D(port='COM8')
92
+ psu = PSU.GPD3303D(emul=True)
93
+ except:
94
+ print('No PSU found, or device is not connected')
95
+ exit()
96
+
97
+ psu.connect()
98
+
99
+ if psu.is_connected():
100
+ HV = HV_control(psu, emul=True)
101
+
102
+ HV.switch_on()
103
+ HV.set_hv(1.5)
104
+ time.sleep(2)
105
+ actual_HV = HV.get_hv()
106
+ print(f'Actual HV = {actual_HV} kV')
107
+ psu.disable_output()
108
+ psu.close_connection()
109
+
110
+ else:
111
+ print('PSU not connected')
112
+
113
+
114
+ '''
115
+ # high voltage callibration
116
+ Vctrl V kV(cur)
117
+ 0.1, 0.025127956
118
+ 0.2, 0.085740858
119
+ 0.3, 0.146705996
120
+ 0.4, 0.207464024
121
+ 0.5, 0.26828344
122
+ 0.6, 0.328773566
123
+ 0.7, 0.389337894
124
+ 0.8, 0.449891494
125
+ 1.3, 0.753993044
126
+ 1.8, 1.053181468
127
+ 2.3, 1.354698166
128
+ 2.8, 1.654354748
129
+ 3.3, 1.955916146
130
+ 3.8, 2.276596628
131
+ 4.3, 2.578250108
132
+ 4.8, 2.875189526
133
+ '''
File without changes