ents 2.3.2__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.
- ents/__init__.py +23 -0
- ents/calibrate/PingSMU.py +51 -0
- ents/calibrate/PingSPS.py +66 -0
- ents/calibrate/README.md +3 -0
- ents/calibrate/__init__.py +0 -0
- ents/calibrate/linear_regression.py +78 -0
- ents/calibrate/plots.py +83 -0
- ents/calibrate/recorder.py +678 -0
- ents/calibrate/requirements.txt +9 -0
- ents/cli.py +546 -0
- ents/config/README.md +123 -0
- ents/config/__init__.py +1 -0
- ents/config/adv_trace.py +56 -0
- ents/config/user_config.py +935 -0
- ents/proto/__init__.py +33 -0
- ents/proto/decode.py +106 -0
- ents/proto/encode.py +298 -0
- ents/proto/esp32.py +179 -0
- ents/proto/soil_power_sensor_pb2.py +72 -0
- ents/simulator/__init__.py +0 -0
- ents/simulator/node.py +161 -0
- ents-2.3.2.dist-info/METADATA +206 -0
- ents-2.3.2.dist-info/RECORD +26 -0
- ents-2.3.2.dist-info/WHEEL +4 -0
- ents-2.3.2.dist-info/entry_points.txt +5 -0
- ents-2.3.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
"""Data recorder for Soil Power Sensor
|
|
4
|
+
|
|
5
|
+
The recorder controls the SPS firmware and a Keithley 2400 Source
|
|
6
|
+
Measurement Unit (SMU). Ideally the script should work with any microcontrollre
|
|
7
|
+
flashed with the firmware and any source measurement unit that supports
|
|
8
|
+
Standard Commands for Programable Instruments (SCPI). The units listed are the
|
|
9
|
+
ones that the script was developed and tested on. It allows to step through
|
|
10
|
+
a range of output voltages on the Keithley and measure the voltage and current
|
|
11
|
+
from both the SMU and the Soil Power Sensor (SPS).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import time
|
|
15
|
+
import socket
|
|
16
|
+
import serial
|
|
17
|
+
from typing import Tuple
|
|
18
|
+
from tqdm import tqdm
|
|
19
|
+
from ..proto import decode_measurement
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SerialController:
|
|
23
|
+
"""Generic serial controller that will open and close serial connections"""
|
|
24
|
+
|
|
25
|
+
# Serial port
|
|
26
|
+
ser = None
|
|
27
|
+
|
|
28
|
+
def __init__(self, port, baudrate=115200, xonxoff=False):
|
|
29
|
+
"""Constructor
|
|
30
|
+
|
|
31
|
+
Initialises connection to serial port.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
port : str
|
|
36
|
+
Serial port of device
|
|
37
|
+
baudrate : int, optional
|
|
38
|
+
Baud rate for serial communication (default is 115200, STM32 functions at 115200)
|
|
39
|
+
xonxoff : bool, optional
|
|
40
|
+
Flow control (default is on)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
self.ser = serial.Serial(port, baudrate=baudrate, xonxoff=xonxoff)
|
|
44
|
+
|
|
45
|
+
def __del__(self):
|
|
46
|
+
"""Destructor
|
|
47
|
+
|
|
48
|
+
Closes connection to serial port.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
self.ser.close()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class LANController:
|
|
55
|
+
"""Generic LAN controller that will open and close LAN connections"""
|
|
56
|
+
|
|
57
|
+
# Socket
|
|
58
|
+
sock = None
|
|
59
|
+
|
|
60
|
+
def __init__(self, host, port):
|
|
61
|
+
"""Constructor
|
|
62
|
+
|
|
63
|
+
Initialises connection to LAN device.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
host : str
|
|
68
|
+
IP address or hostname of the device
|
|
69
|
+
port : int
|
|
70
|
+
Port number of the device
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
74
|
+
self.sock.connect((host, port))
|
|
75
|
+
|
|
76
|
+
def __del__(self):
|
|
77
|
+
"""Destructor
|
|
78
|
+
|
|
79
|
+
Closes connection to LAN device.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
self.sock.close()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class SoilPowerSensorController(SerialController):
|
|
86
|
+
"""Controller used to read values from the SPS"""
|
|
87
|
+
|
|
88
|
+
def __init__(self, port):
|
|
89
|
+
"""Constructor
|
|
90
|
+
|
|
91
|
+
Opens serial connection and checks functionality
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
port : str
|
|
96
|
+
Serial port of device
|
|
97
|
+
"""
|
|
98
|
+
super().__init__(port)
|
|
99
|
+
self.check()
|
|
100
|
+
|
|
101
|
+
def get_power(self) -> Tuple[float, float]:
|
|
102
|
+
"""Measure voltage from SPS
|
|
103
|
+
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
tuple[float, float
|
|
107
|
+
voltage, current
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
self.ser.write(b"0") # send a command to the SPS to send a power measurment
|
|
111
|
+
|
|
112
|
+
# read a single byte for the length
|
|
113
|
+
resp_len_bytes = self.ser.read()
|
|
114
|
+
|
|
115
|
+
resp_len = int.from_bytes(resp_len_bytes)
|
|
116
|
+
|
|
117
|
+
reply = self.ser.read(resp_len) # read said measurment
|
|
118
|
+
|
|
119
|
+
meas_dict = decode_measurement(reply) # decode using protobuf
|
|
120
|
+
|
|
121
|
+
voltage_value = meas_dict["data"]["voltage"]
|
|
122
|
+
current_value = meas_dict["data"]["current"]
|
|
123
|
+
|
|
124
|
+
return float(voltage_value), float(current_value)
|
|
125
|
+
|
|
126
|
+
def check(self):
|
|
127
|
+
"""Performs a check of the connection to the board
|
|
128
|
+
|
|
129
|
+
Raises
|
|
130
|
+
------
|
|
131
|
+
RuntimeError
|
|
132
|
+
Checks that SPS replies "ok" when sent "check"
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
# needed sleep to get write to work
|
|
136
|
+
# possibly due to linux usb stack initialized or mcu waiting to startup
|
|
137
|
+
time.sleep(1)
|
|
138
|
+
self.ser.write(b"check\n")
|
|
139
|
+
|
|
140
|
+
reply = self.ser.readline()
|
|
141
|
+
# reply = reply.decode()
|
|
142
|
+
# reply = reply.strip("\r\n")
|
|
143
|
+
|
|
144
|
+
if reply != b"ok\n":
|
|
145
|
+
raise RuntimeError(f"SPS check failed. Reply received: {reply}")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class SMUSerialController(SerialController):
|
|
149
|
+
"""Controller for the Keithley 2400 SMU used to supply known voltage to the
|
|
150
|
+
SPS
|
|
151
|
+
|
|
152
|
+
Uses SCPI (Standard Control of Programmable Instruments) to control the SMU
|
|
153
|
+
through its RS232 port. Written for the Keithley 2400 SMU, but should work
|
|
154
|
+
for any other SMU that uses SCPI.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
class VoltageIterator:
|
|
158
|
+
"""VoltageIterator Class
|
|
159
|
+
|
|
160
|
+
Implements a iterator for looping through voltage output values
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
def __init__(self, ser, start, stop, step):
|
|
164
|
+
"""Constructor
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
ser : serial.Serial
|
|
169
|
+
Initialised serial connection
|
|
170
|
+
start : float
|
|
171
|
+
Starting voltage
|
|
172
|
+
stop : float
|
|
173
|
+
End voltage
|
|
174
|
+
step : float
|
|
175
|
+
Voltage step
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
self.ser = ser
|
|
179
|
+
self.start = start
|
|
180
|
+
self.stop = stop
|
|
181
|
+
self.step = step
|
|
182
|
+
|
|
183
|
+
def __iter__(self):
|
|
184
|
+
"""Iterator
|
|
185
|
+
|
|
186
|
+
Sets current value to start
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
self.v = None
|
|
190
|
+
self.ser.write(b":OUTP ON\n")
|
|
191
|
+
return self
|
|
192
|
+
|
|
193
|
+
def __next__(self):
|
|
194
|
+
"""Next
|
|
195
|
+
|
|
196
|
+
Steps to next voltage level, stopping once stop is reached
|
|
197
|
+
|
|
198
|
+
Raises
|
|
199
|
+
------
|
|
200
|
+
StopIteration
|
|
201
|
+
When the next step exceeds the stop level
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
if self.v is None:
|
|
205
|
+
return self.set_voltage(self.start)
|
|
206
|
+
|
|
207
|
+
v_next = self.v + self.step
|
|
208
|
+
|
|
209
|
+
if v_next <= self.stop:
|
|
210
|
+
return self.set_voltage(v_next)
|
|
211
|
+
else:
|
|
212
|
+
raise StopIteration
|
|
213
|
+
|
|
214
|
+
def set_voltage(self, v):
|
|
215
|
+
"""Sets the voltage output"""
|
|
216
|
+
|
|
217
|
+
self.v = v
|
|
218
|
+
cmd = f":SOUR:VOLT:LEV {v}\n"
|
|
219
|
+
self.ser.write(bytes(cmd, "ascii"))
|
|
220
|
+
return self.v
|
|
221
|
+
|
|
222
|
+
def __init__(self, port, source_mode):
|
|
223
|
+
"""Constructor
|
|
224
|
+
|
|
225
|
+
Opens serial port, restore to known defaults
|
|
226
|
+
|
|
227
|
+
Parameters
|
|
228
|
+
----------
|
|
229
|
+
port : str
|
|
230
|
+
Serial port of device
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
super().__init__(port)
|
|
234
|
+
# Reset settings
|
|
235
|
+
self.ser.write(b"*RST\n")
|
|
236
|
+
# Voltage source
|
|
237
|
+
self.ser.write(b":SOUR:FUNC VOLT\n")
|
|
238
|
+
self.ser.write(b":SOUR:VOLT:MODE FIXED\n")
|
|
239
|
+
# 1mA compliance
|
|
240
|
+
self.ser.write(b":SENS:CURR:PROT 10e-3\n")
|
|
241
|
+
# Sensing functions
|
|
242
|
+
self.ser.write(b":SENS:CURR:RANGE:AUTO ON\n")
|
|
243
|
+
self.ser.write(b":SENS:FUNC:OFF:ALL\n")
|
|
244
|
+
self.ser.write(b':SENS:FUNC:ON "VOLT"\n')
|
|
245
|
+
self.ser.write(b':SENS:FUNC:ON "CURR"\n')
|
|
246
|
+
|
|
247
|
+
def __del__(self):
|
|
248
|
+
"""Destructor
|
|
249
|
+
|
|
250
|
+
Turns off output
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
self.ser.write(b":OUTP OFF\n")
|
|
254
|
+
|
|
255
|
+
def vrange(self, start, stop, step) -> VoltageIterator:
|
|
256
|
+
"""Gets iterator to range of voltages
|
|
257
|
+
|
|
258
|
+
Parameters
|
|
259
|
+
----------
|
|
260
|
+
start : float
|
|
261
|
+
Starting voltage
|
|
262
|
+
stop : float
|
|
263
|
+
End voltage
|
|
264
|
+
step : float
|
|
265
|
+
Voltage step
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
return self.VoltageIterator(self.ser, start, stop, step)
|
|
269
|
+
|
|
270
|
+
def get_voltage(self) -> float:
|
|
271
|
+
"""Measure voltage supplied to the SPS from SMU
|
|
272
|
+
|
|
273
|
+
Returns
|
|
274
|
+
-------
|
|
275
|
+
float
|
|
276
|
+
Measured voltage
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
self.ser.write(b":FORM:ELEM VOLT\n")
|
|
280
|
+
self.ser.write(b":READ?\n")
|
|
281
|
+
reply = self.ser.readline().decode()
|
|
282
|
+
reply = reply.strip("\r")
|
|
283
|
+
return float(reply)
|
|
284
|
+
|
|
285
|
+
def get_current(self) -> float:
|
|
286
|
+
"""Measure current supplied to the SPS from SMU
|
|
287
|
+
|
|
288
|
+
Returns
|
|
289
|
+
-------
|
|
290
|
+
float
|
|
291
|
+
Measured current
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
self.ser.write(
|
|
295
|
+
b":FORM:ELEM CURR\n"
|
|
296
|
+
) # replace with serial.write with socket.write commands, std library aviable, lots of example code
|
|
297
|
+
self.ser.write(b":READ?\n")
|
|
298
|
+
reply = self.ser.readline().decode()
|
|
299
|
+
reply = reply.strip("\r")
|
|
300
|
+
return float(reply)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class SMULANController(LANController):
|
|
304
|
+
"""Controller for the Keithley 2400 SMU used to supply known voltage to the
|
|
305
|
+
SPS
|
|
306
|
+
|
|
307
|
+
Uses SCPI (Standard Control of Programmable Instruments) to control the SMU
|
|
308
|
+
through its RS232 port. Written for the Keithley 2400 SMU, but should work
|
|
309
|
+
for any other SMU that uses SCPI.
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
class VoltageIterator:
|
|
313
|
+
"""VoltageIterator Class
|
|
314
|
+
|
|
315
|
+
Implements a iterator for looping through voltage output values
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
def __init__(self, sock, start, stop, step):
|
|
319
|
+
"""Constructor
|
|
320
|
+
|
|
321
|
+
Parameters
|
|
322
|
+
----------
|
|
323
|
+
sock : serial.Socket
|
|
324
|
+
Initialised socket connection
|
|
325
|
+
start : float
|
|
326
|
+
Starting voltage
|
|
327
|
+
stop : float
|
|
328
|
+
End voltage
|
|
329
|
+
step : float
|
|
330
|
+
Voltage step
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
self.sock = sock
|
|
334
|
+
self.start = start
|
|
335
|
+
self.stop = stop
|
|
336
|
+
self.step = step
|
|
337
|
+
|
|
338
|
+
def __iter__(self):
|
|
339
|
+
"""Iterator
|
|
340
|
+
|
|
341
|
+
Sets current value to start
|
|
342
|
+
"""
|
|
343
|
+
|
|
344
|
+
self.v = None
|
|
345
|
+
self.sock.sendall(b":OUTP ON\n")
|
|
346
|
+
return self
|
|
347
|
+
|
|
348
|
+
def __next__(self):
|
|
349
|
+
"""Next
|
|
350
|
+
|
|
351
|
+
Steps to next voltage level, stopping once stop is reached
|
|
352
|
+
|
|
353
|
+
Raises
|
|
354
|
+
------
|
|
355
|
+
StopIteration
|
|
356
|
+
When the next step exceeds the stop level
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
if self.v is None:
|
|
360
|
+
return self.set_voltage(self.start)
|
|
361
|
+
|
|
362
|
+
v_next = self.v + self.step
|
|
363
|
+
|
|
364
|
+
if v_next <= self.stop:
|
|
365
|
+
return self.set_voltage(v_next)
|
|
366
|
+
else:
|
|
367
|
+
raise StopIteration
|
|
368
|
+
|
|
369
|
+
def __len__(self):
|
|
370
|
+
"""Len
|
|
371
|
+
|
|
372
|
+
The number of measurements points
|
|
373
|
+
"""
|
|
374
|
+
return int((self.stop - self.start) / self.step) + 1
|
|
375
|
+
|
|
376
|
+
def set_voltage(self, v):
|
|
377
|
+
"""Sets the voltage output"""
|
|
378
|
+
|
|
379
|
+
self.v = v
|
|
380
|
+
cmd = f":SOUR:VOLT:LEV {v}\n"
|
|
381
|
+
self.sock.sendall(bytes(cmd, "ascii"))
|
|
382
|
+
return self.v
|
|
383
|
+
|
|
384
|
+
class CurrentIterator:
|
|
385
|
+
"""CurrentIterator Class
|
|
386
|
+
|
|
387
|
+
Implements a iterator for looping through current output values
|
|
388
|
+
"""
|
|
389
|
+
|
|
390
|
+
def __init__(self, sock, start, stop, step):
|
|
391
|
+
"""Constructor
|
|
392
|
+
|
|
393
|
+
Parameters
|
|
394
|
+
----------
|
|
395
|
+
sock : serial.Socket
|
|
396
|
+
Initialised socket connection
|
|
397
|
+
start : float
|
|
398
|
+
Starting current
|
|
399
|
+
stop : float
|
|
400
|
+
End current
|
|
401
|
+
step : float
|
|
402
|
+
Current step
|
|
403
|
+
"""
|
|
404
|
+
self.sock = sock
|
|
405
|
+
self.start = start
|
|
406
|
+
self.stop = stop
|
|
407
|
+
self.step = step
|
|
408
|
+
|
|
409
|
+
def __iter__(self):
|
|
410
|
+
"""Iterator
|
|
411
|
+
|
|
412
|
+
Sets current value to start
|
|
413
|
+
"""
|
|
414
|
+
|
|
415
|
+
self.i = None
|
|
416
|
+
self.sock.sendall(b":OUTP ON\n")
|
|
417
|
+
return self
|
|
418
|
+
|
|
419
|
+
def __next__(self):
|
|
420
|
+
"""Next
|
|
421
|
+
|
|
422
|
+
Steps to next voltage level, stopping once stop is reached
|
|
423
|
+
|
|
424
|
+
Raises
|
|
425
|
+
------
|
|
426
|
+
StopIteration
|
|
427
|
+
When the next step exceeds the stop level
|
|
428
|
+
"""
|
|
429
|
+
|
|
430
|
+
if self.i is None:
|
|
431
|
+
return self.set_current(self.start)
|
|
432
|
+
i_next = self.i + self.step
|
|
433
|
+
if i_next <= self.stop:
|
|
434
|
+
return self.set_current(i_next)
|
|
435
|
+
else:
|
|
436
|
+
raise StopIteration
|
|
437
|
+
|
|
438
|
+
def __len__(self):
|
|
439
|
+
"""Len
|
|
440
|
+
|
|
441
|
+
The number of measurements points
|
|
442
|
+
"""
|
|
443
|
+
return int((self.stop - self.start) / self.step) + 1
|
|
444
|
+
|
|
445
|
+
def set_current(self, i):
|
|
446
|
+
"""Sets the current output"""
|
|
447
|
+
|
|
448
|
+
self.i = i
|
|
449
|
+
cmd = f":SOUR:CURR:LEV {i}\n"
|
|
450
|
+
self.sock.sendall(bytes(cmd, "ascii"))
|
|
451
|
+
return self.i
|
|
452
|
+
|
|
453
|
+
def __init__(self, host, port):
|
|
454
|
+
"""Constructor
|
|
455
|
+
|
|
456
|
+
Opens LAN connection and sets initial SMU configurations.
|
|
457
|
+
|
|
458
|
+
Parameters
|
|
459
|
+
----------
|
|
460
|
+
host : str
|
|
461
|
+
IP address or hostname of the SMU
|
|
462
|
+
port : int
|
|
463
|
+
Port number used for the LAN connection
|
|
464
|
+
"""
|
|
465
|
+
|
|
466
|
+
super().__init__(host, port)
|
|
467
|
+
|
|
468
|
+
def setup_voltage(self):
|
|
469
|
+
"""Configures smu for sourcing voltage"""
|
|
470
|
+
|
|
471
|
+
self.sock.sendall(b"*RST\n")
|
|
472
|
+
# Voltage source
|
|
473
|
+
self.sock.sendall(b":SOUR:FUNC VOLT\n")
|
|
474
|
+
self.sock.sendall(b":SOUR:VOLT:MODE FIXED\n")
|
|
475
|
+
# 1mA compliance
|
|
476
|
+
self.sock.sendall(b":SENS:CURR:PROT 10e-3\n")
|
|
477
|
+
# Sensing functions
|
|
478
|
+
self.sock.sendall(b":SENS:CURR:RANGE:AUTO ON\n")
|
|
479
|
+
self.sock.sendall(b":SENS:FUNC:OFF:ALL\n")
|
|
480
|
+
self.sock.sendall(b':SENS:FUNC:ON "VOLT"\n')
|
|
481
|
+
self.sock.sendall(b':SENS:FUNC:ON "CURR"\n')
|
|
482
|
+
|
|
483
|
+
def setup_current(self):
|
|
484
|
+
"""Configured smu for sourcing current"""
|
|
485
|
+
|
|
486
|
+
self.sock.sendall(b"*RST\n")
|
|
487
|
+
# Current source
|
|
488
|
+
self.sock.sendall(b":SOUR:FUNC CURR\n")
|
|
489
|
+
self.sock.sendall(b":SOUR:CURR:MODE FIXED\n")
|
|
490
|
+
# 1V compliance
|
|
491
|
+
self.sock.sendall(b":SENSE:CURR:PROT 1\n")
|
|
492
|
+
# Sensing functions
|
|
493
|
+
self.sock.sendall(b":SENS:VOLT:RANGE:AUTO ON\n")
|
|
494
|
+
self.sock.sendall(b":SENS:FUNC:OFF:ALL\n")
|
|
495
|
+
self.sock.sendall(b':SENS:FUNC:ON "CURR"\n')
|
|
496
|
+
self.sock.sendall(b':SENS:FUNC:ON "VOLT"\n')
|
|
497
|
+
|
|
498
|
+
def __del__(self):
|
|
499
|
+
"""Destructor
|
|
500
|
+
|
|
501
|
+
Turns off output
|
|
502
|
+
"""
|
|
503
|
+
|
|
504
|
+
self.sock.sendall(b":OUTP OFF\n")
|
|
505
|
+
|
|
506
|
+
def vrange(self, start, stop, step) -> VoltageIterator:
|
|
507
|
+
"""Gets iterator to range of voltages
|
|
508
|
+
|
|
509
|
+
Parameters
|
|
510
|
+
----------
|
|
511
|
+
start : float
|
|
512
|
+
Starting voltage
|
|
513
|
+
stop : float
|
|
514
|
+
End voltage
|
|
515
|
+
step : float
|
|
516
|
+
Voltage step
|
|
517
|
+
"""
|
|
518
|
+
|
|
519
|
+
self.setup_voltage()
|
|
520
|
+
return self.VoltageIterator(self.sock, start, stop, step)
|
|
521
|
+
|
|
522
|
+
def irange(self, start, stop, step) -> CurrentIterator:
|
|
523
|
+
"""Gets iterator to range of currents
|
|
524
|
+
|
|
525
|
+
Parameters
|
|
526
|
+
----------
|
|
527
|
+
start : float
|
|
528
|
+
Starting current
|
|
529
|
+
stop : float
|
|
530
|
+
End current
|
|
531
|
+
step : float
|
|
532
|
+
Current step
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
self.setup_current()
|
|
536
|
+
return self.CurrentIterator(self.sock, start, stop, step)
|
|
537
|
+
|
|
538
|
+
def get_voltage(self) -> float:
|
|
539
|
+
"""Measure voltage supplied to the SPS from SMU
|
|
540
|
+
|
|
541
|
+
Returns
|
|
542
|
+
-------
|
|
543
|
+
float
|
|
544
|
+
Measured voltage
|
|
545
|
+
"""
|
|
546
|
+
|
|
547
|
+
self.sock.sendall(b":FORM:ELEM VOLT\n")
|
|
548
|
+
self.sock.sendall(b":READ?\n")
|
|
549
|
+
# receive response
|
|
550
|
+
reply = self.sock.recv(256)
|
|
551
|
+
# strip trailing \r\n characters
|
|
552
|
+
reply = reply.strip()
|
|
553
|
+
# convert to float and return
|
|
554
|
+
return float(reply)
|
|
555
|
+
|
|
556
|
+
def get_current(self) -> float:
|
|
557
|
+
"""Measure current supplied to the SPS from SMU
|
|
558
|
+
|
|
559
|
+
Returns
|
|
560
|
+
-------
|
|
561
|
+
float
|
|
562
|
+
Measured current
|
|
563
|
+
"""
|
|
564
|
+
|
|
565
|
+
self.sock.sendall(
|
|
566
|
+
b":FORM:ELEM CURR\n"
|
|
567
|
+
) # replace with serial.write with socket.write commands, std library aviable, lots of example code
|
|
568
|
+
self.sock.sendall(b":READ?\n")
|
|
569
|
+
# receive response
|
|
570
|
+
reply = self.sock.recv(256)
|
|
571
|
+
# strip trailing \r\n characters
|
|
572
|
+
reply = reply.strip()
|
|
573
|
+
# convert to float and return
|
|
574
|
+
return float(reply)
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
class Recorder:
|
|
578
|
+
def __init__(self, serialport: str, host: str, port: int, delay: int = 1):
|
|
579
|
+
"""Recorder constructor
|
|
580
|
+
|
|
581
|
+
Initializes the connection to smu and sps.
|
|
582
|
+
|
|
583
|
+
Args:
|
|
584
|
+
serialport: Serial port to sps
|
|
585
|
+
host: Hostname/ip of smu
|
|
586
|
+
port: TCP port to smu
|
|
587
|
+
delay: Delay between smu step and power measurement in seconds
|
|
588
|
+
"""
|
|
589
|
+
|
|
590
|
+
self.sps = SoilPowerSensorController(serialport)
|
|
591
|
+
self.smu = SMULANController(host, port)
|
|
592
|
+
self.delay = delay
|
|
593
|
+
|
|
594
|
+
def record_voltage(
|
|
595
|
+
self, start: float, stop: float, step: float, samples: int
|
|
596
|
+
) -> dict[str, list]:
|
|
597
|
+
"""Records voltage measurements from the smu and sps
|
|
598
|
+
|
|
599
|
+
Input arguments given in volts units. Returned dictionary has keys for the
|
|
600
|
+
expected voltage (number from python), actual voltage (value measured by
|
|
601
|
+
the smu), and meas voltage (value measured by the sps).
|
|
602
|
+
|
|
603
|
+
Args:
|
|
604
|
+
start: Start voltage
|
|
605
|
+
stop: Stop voltage (inclusive)
|
|
606
|
+
step: Step between voltage
|
|
607
|
+
samples: Number of samples taken at each voltage
|
|
608
|
+
|
|
609
|
+
Returns:
|
|
610
|
+
Dictionary in the following pandas compatable format:
|
|
611
|
+
{
|
|
612
|
+
"expected": [],
|
|
613
|
+
"actual": [],
|
|
614
|
+
"meas": [],
|
|
615
|
+
}
|
|
616
|
+
"""
|
|
617
|
+
|
|
618
|
+
data = {
|
|
619
|
+
"expected": [],
|
|
620
|
+
"actual": [],
|
|
621
|
+
"meas": [],
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
for value in tqdm(self.smu.vrange(start, stop, step)):
|
|
625
|
+
time.sleep(self.delay)
|
|
626
|
+
for _ in range(samples):
|
|
627
|
+
# expected
|
|
628
|
+
data["expected"].append(value)
|
|
629
|
+
# smu
|
|
630
|
+
data["actual"].append(self.smu.get_voltage())
|
|
631
|
+
# sps
|
|
632
|
+
sps_v, _ = self.sps.get_power()
|
|
633
|
+
data["meas"].append(sps_v)
|
|
634
|
+
|
|
635
|
+
return data
|
|
636
|
+
|
|
637
|
+
def record_current(
|
|
638
|
+
self, start: float, stop: float, step: float, samples: int
|
|
639
|
+
) -> dict[str, list]:
|
|
640
|
+
"""Records current measurements from the smu and sps
|
|
641
|
+
|
|
642
|
+
Input arguments given in amps units. Returned dictionary has keys for the
|
|
643
|
+
expected current (number from python), actual current (value measured by
|
|
644
|
+
the smu), and meas current (value measured by the sps).
|
|
645
|
+
|
|
646
|
+
Args:
|
|
647
|
+
start: Start current
|
|
648
|
+
stop: Stop current (inclusive)
|
|
649
|
+
step: Step between current
|
|
650
|
+
samples: Number of samples taken at each voltage
|
|
651
|
+
|
|
652
|
+
Returns:
|
|
653
|
+
Dictionary in the following pandas compatable format:
|
|
654
|
+
{
|
|
655
|
+
"expected": [],
|
|
656
|
+
"actual": [],
|
|
657
|
+
"meas": [],
|
|
658
|
+
}
|
|
659
|
+
"""
|
|
660
|
+
|
|
661
|
+
data = {
|
|
662
|
+
"expected": [],
|
|
663
|
+
"actual": [],
|
|
664
|
+
"meas": [],
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
for value in tqdm(self.smu.irange(start, stop, step)):
|
|
668
|
+
time.sleep(self.delay)
|
|
669
|
+
for _ in range(samples):
|
|
670
|
+
# expected
|
|
671
|
+
data["expected"].append(value)
|
|
672
|
+
# smu
|
|
673
|
+
data["actual"].append(self.smu.get_current())
|
|
674
|
+
# sps
|
|
675
|
+
_, sps_i = self.sps.get_power()
|
|
676
|
+
data["meas"].append(sps_i)
|
|
677
|
+
|
|
678
|
+
return data
|