23
23
logger = logging .getLogger (__name__ )
24
24
25
25
26
+ def detect ():
27
+ """Detect connected PSLab devices.
28
+
29
+ Returns
30
+ -------
31
+ devices : dict of str: str
32
+ Dictionary containing port name as keys and device version on that
33
+ port as values.
34
+ """
35
+ regex = []
36
+
37
+ for vid , pid in zip (SerialHandler ._USB_VID , SerialHandler ._USB_PID ):
38
+ regex .append (f"{ vid :04x} :{ pid :04x} " )
39
+
40
+ regex = "(" + "|" .join (regex ) + ")"
41
+ port_info_generator = list_ports .grep (regex )
42
+ pslab_devices = {}
43
+
44
+ for port_info in port_info_generator :
45
+ version = _get_version (port_info .device )
46
+ if any (expected in version for expected in ["PSLab" , "CSpark" ]):
47
+ pslab_devices [port_info .device ] = version
48
+
49
+ return pslab_devices
50
+
51
+
52
+ def _get_version (port : str ) -> str :
53
+ interface = serial .Serial (port = port , baudrate = 1e6 , timeout = 1 )
54
+ interface .write (CP .COMMON )
55
+ interface .write (CP .GET_VERSION )
56
+ version = interface .readline ()
57
+ return version .decode ("utf-8" )
58
+
59
+
26
60
class SerialHandler :
27
61
"""Provides methods for communicating with the PSLab hardware.
28
62
@@ -98,9 +132,11 @@ def connect(
98
132
Parameters
99
133
----------
100
134
port : str, optional
101
- The name of the port to which the PSLab is connected as a string. On
102
- Posix this is a path, e.g. "/dev/ttyACM0". On Windows, it's a numbered
103
- COM port, e.g. "COM5". Will be autodetected if not specified.
135
+ The name of the port to which the PSLab is connected as a string.
136
+ On Posix this is a path, e.g. "/dev/ttyACM0". On Windows, it's a
137
+ numbered COM port, e.g. "COM5". Will be autodetected if not
138
+ specified. If multiple PSLab devices are connected, port must be
139
+ specified.
104
140
baudrate : int, optional
105
141
Symbol rate in bit/s. The default value is 1000000.
106
142
timeout : float, optional
@@ -111,6 +147,8 @@ def connect(
111
147
------
112
148
SerialException
113
149
If connection could not be established.
150
+ RuntimeError
151
+ If ultiple devices are connected and no port was specified.
114
152
"""
115
153
# serial.Serial opens automatically if port is not None.
116
154
self .interface = serial .Serial (
@@ -119,28 +157,31 @@ def connect(
119
157
timeout = timeout ,
120
158
write_timeout = timeout ,
121
159
)
160
+ pslab_devices = detect ()
122
161
123
162
if self .interface .is_open :
124
163
# User specified a port.
125
164
version = self .get_version ()
126
165
else :
127
- regex = []
128
- for vid , pid in zip (self ._USB_VID , self ._USB_PID ):
129
- regex .append (f"{ vid :04x} :{ pid :04x} " )
130
-
131
- regex = "(" + "|" .join (regex ) + ")"
132
- port_info_generator = list_ports .grep (regex )
133
-
134
- for port_info in port_info_generator :
135
- self .interface .port = port_info .device
166
+ if len (pslab_devices ) == 1 :
167
+ self .interface .port = list (pslab_devices .keys ())[0 ]
136
168
self .interface .open ()
137
169
version = self .get_version ()
138
- if any (expected in version for expected in ["PSLab" , "CSpark" ]):
139
- break
170
+ elif len (pslab_devices ) > 1 :
171
+ found = ""
172
+
173
+ for port , version in pslab_devices .items ():
174
+ found += f"{ port } : { version } "
175
+
176
+ raise RuntimeError (
177
+ "Multiple PSLab devices found:\n "
178
+ f"{ found } "
179
+ "Please choose a device by specifying a port."
180
+ )
140
181
else :
141
182
version = ""
142
183
143
- if any ( expected in version for expected in [ "PSLab" , "CSpark" ]) :
184
+ if self . interface . port in pslab_devices :
144
185
self .version = version
145
186
logger .info (f"Connected to { self .version } on { self .interface .port } ." )
146
187
else :
@@ -174,13 +215,11 @@ def reconnect(
174
215
port = self .interface .port if port is None else port
175
216
timeout = self .interface .timeout if timeout is None else timeout
176
217
177
- self .interface = serial . Serial (
218
+ self .connect (
178
219
port = port ,
179
220
baudrate = baudrate ,
180
221
timeout = timeout ,
181
- write_timeout = timeout ,
182
222
)
183
- self .connect ()
184
223
185
224
def get_version (self ) -> str :
186
225
"""Query PSLab for its version and return it as a decoded string.
0 commit comments