YorkUphysLabV2 2.0.0__tar.gz
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.
- yorkuphyslabv2-2.0.0/LICENSE +21 -0
- yorkuphyslabv2-2.0.0/PKG-INFO +43 -0
- yorkuphyslabv2-2.0.0/README.md +24 -0
- yorkuphyslabv2-2.0.0/YorkUphysLab/Actuator/Actuator.py +133 -0
- yorkuphyslabv2-2.0.0/YorkUphysLab/Actuator/__init__.py +0 -0
- yorkuphyslabv2-2.0.0/YorkUphysLab/GwINSTEK/AFG2000.py +164 -0
- yorkuphyslabv2-2.0.0/YorkUphysLab/GwINSTEK/GPD3303D.py +218 -0
- yorkuphyslabv2-2.0.0/YorkUphysLab/GwINSTEK/__init__.py +0 -0
- yorkuphyslabv2-2.0.0/YorkUphysLab/HVcontrol/HV_control.py +133 -0
- yorkuphyslabv2-2.0.0/YorkUphysLab/HVcontrol/__init__.py +0 -0
- yorkuphyslabv2-2.0.0/YorkUphysLab/ScoutScale/ScoutSTX.py +131 -0
- yorkuphyslabv2-2.0.0/YorkUphysLab/ScoutScale/__init__.py +0 -0
- yorkuphyslabv2-2.0.0/YorkUphysLab/Tektronix/TBS1000.py +456 -0
- yorkuphyslabv2-2.0.0/YorkUphysLab/Tektronix/__init__.py +0 -0
- yorkuphyslabv2-2.0.0/YorkUphysLab/Utility/Utility.py +56 -0
- yorkuphyslabv2-2.0.0/YorkUphysLab/Utility/__init__.py +0 -0
- yorkuphyslabv2-2.0.0/YorkUphysLab/__init__.py +1 -0
- yorkuphyslabv2-2.0.0/YorkUphysLab/__version__.py +1 -0
- yorkuphyslabv2-2.0.0/YorkUphysLabV2.egg-info/PKG-INFO +43 -0
- yorkuphyslabv2-2.0.0/YorkUphysLabV2.egg-info/SOURCES.txt +24 -0
- yorkuphyslabv2-2.0.0/YorkUphysLabV2.egg-info/dependency_links.txt +1 -0
- yorkuphyslabv2-2.0.0/YorkUphysLabV2.egg-info/requires.txt +4 -0
- yorkuphyslabv2-2.0.0/YorkUphysLabV2.egg-info/top_level.txt +3 -0
- yorkuphyslabv2-2.0.0/example/YorkUphysLab_example.py +50 -0
- yorkuphyslabv2-2.0.0/pyproject.toml +33 -0
- yorkuphyslabv2-2.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Mohammad Kareem
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: YorkUphysLabV2
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: A Python package to control YU physics labs equipment. V2 updated to work with new versions of setup.
|
|
5
|
+
Author-email: "Bruce Howard (maintainer)" <blhoward@yorku.ca>, "Mohammad Kareem (original author)" <mkareem@yorku.ca>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/brucehoward-physics/YorkUphysLab
|
|
8
|
+
Project-URL: Issues, https://github.com/m-kareem/YorkUphysLab/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: nidaqmx
|
|
15
|
+
Requires-Dist: pyserial
|
|
16
|
+
Requires-Dist: pyvisa-py
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# York University physics lab
|
|
21
|
+
|
|
22
|
+
This package includes the required drivers to control some of the hardwares in physics labs at York University.
|
|
23
|
+
|
|
24
|
+
## How to install
|
|
25
|
+
|
|
26
|
+
```pip install --no-cache-dir --upgrade YorkUphysLabV2```
|
|
27
|
+
|
|
28
|
+
## How to use
|
|
29
|
+
|
|
30
|
+
See the examples script: YorkUphysLab_example.py
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## Developed by:
|
|
34
|
+
|
|
35
|
+
Mohammad Kareem, Ph.D.
|
|
36
|
+
Laboratory Technologist
|
|
37
|
+
Department of Physics & Astronomy
|
|
38
|
+
Faculty of Science | YORK UNIVERSITY
|
|
39
|
+
4700 Keele Street, Toronto, Ontario, Canada M3J 1P3
|
|
40
|
+
|
|
41
|
+
## Maintained in V2 by:
|
|
42
|
+
|
|
43
|
+
Bruce Howard, Ph.D.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# York University physics lab
|
|
2
|
+
|
|
3
|
+
This package includes the required drivers to control some of the hardwares in physics labs at York University.
|
|
4
|
+
|
|
5
|
+
## How to install
|
|
6
|
+
|
|
7
|
+
```pip install --no-cache-dir --upgrade YorkUphysLabV2```
|
|
8
|
+
|
|
9
|
+
## How to use
|
|
10
|
+
|
|
11
|
+
See the examples script: YorkUphysLab_example.py
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## Developed by:
|
|
15
|
+
|
|
16
|
+
Mohammad Kareem, Ph.D.
|
|
17
|
+
Laboratory Technologist
|
|
18
|
+
Department of Physics & Astronomy
|
|
19
|
+
Faculty of Science | YORK UNIVERSITY
|
|
20
|
+
4700 Keele Street, Toronto, Ontario, Canada M3J 1P3
|
|
21
|
+
|
|
22
|
+
## Maintained in V2 by:
|
|
23
|
+
|
|
24
|
+
Bruce Howard, Ph.D.
|
|
@@ -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
|