add buffer measurement mode
This commit is contained in:
parent
4782adbf6a
commit
c5c016399b
@ -79,7 +79,7 @@ led: LedControlDevice|None = None
|
|||||||
data = DataCollector(data_path=settings["datadir"], data_name="interactive", dirname="interactive_test", dir_exists_is_ok=True)
|
data = DataCollector(data_path=settings["datadir"], data_name="interactive", dirname="interactive_test", dir_exists_is_ok=True)
|
||||||
t0 = 0
|
t0 = 0
|
||||||
|
|
||||||
def monitor(script: str|int=0, interval=None, flush_after=None, max_measurements=None, max_points_shown=None):
|
def monitor(script: str|int=0, interval=None, flush_after=None, use_buffer=False, max_measurements=None, max_points_shown=None):
|
||||||
"""
|
"""
|
||||||
Monitor the voltage with matplotlib.
|
Monitor the voltage with matplotlib.
|
||||||
|
|
||||||
@ -99,21 +99,32 @@ def monitor(script: str|int=0, interval=None, flush_after=None, max_measurements
|
|||||||
plt_monitor = _Monitor(use_print=True, max_points_shown=max_points_shown)
|
plt_monitor = _Monitor(use_print=True, max_points_shown=max_points_shown)
|
||||||
led_script = LedScript(script=script, auto_update=True, verbose=True)
|
led_script = LedScript(script=script, auto_update=True, verbose=True)
|
||||||
data.clear()
|
data.clear()
|
||||||
queue = mp.Queue()
|
data_queue = mp.Queue()
|
||||||
pipe_send, pipe_recv = mp.Pipe()
|
command_queue = mp.Queue()
|
||||||
# TODO: pass instruments
|
# Argument order must match the definition
|
||||||
proc_measure = mt.Thread(target=_measure, args=(dev, led, led_script, data, interval, flush_after, max_measurements, False, pipe_recv, queue))
|
proc_measure = mt.Thread(target=_measure, args=(dev, led, led_script, data,
|
||||||
|
interval,
|
||||||
|
flush_after,
|
||||||
|
use_buffer,
|
||||||
|
max_measurements,
|
||||||
|
False, # verbose
|
||||||
|
command_queue,
|
||||||
|
data_queue
|
||||||
|
))
|
||||||
proc_measure.start()
|
proc_measure.start()
|
||||||
try:
|
try:
|
||||||
while True:
|
while proc_measure.is_alive():
|
||||||
current_data = queue.get(block=True, timeout=30)
|
while not data_queue.empty():
|
||||||
|
# print(data_queue.qsize(), "\n\n")
|
||||||
|
current_data = data_queue.get(block=False)
|
||||||
i, tval, vval, led_val = current_data
|
i, tval, vval, led_val = current_data
|
||||||
plt_monitor.update(i, tval, vval, led_val)
|
plt_monitor.update(i, tval, vval, led_val)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
pipe_send.send("stop")
|
command_queue.put("stop")
|
||||||
proc_measure.join()
|
proc_measure.join()
|
||||||
print(data.metadata)
|
led_script.stop_updating() # stop watching for file updates (if enabled)
|
||||||
data.save_csv(verbose=True)
|
data.save_csv(verbose=True)
|
||||||
|
|
||||||
|
|
||||||
@ -231,12 +242,8 @@ Enter 'help()' for a list of commands""")
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
pass
|
pass
|
||||||
# dev = _volt.init("GPIB0::22::INSTR")
|
dev = _volt.init("GPIB0::22::INSTR")
|
||||||
# TODO
|
led = _led.LEDD1B()
|
||||||
# manager = BaseManager()
|
|
||||||
# manager.start()
|
|
||||||
# led = manager._led.LEDD1B()
|
|
||||||
# led = _led.LEDD1B()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
@ -3,4 +3,3 @@ INIT:CONT OFF
|
|||||||
' two readings per second
|
' two readings per second
|
||||||
TRIGger:SOURce TIMer
|
TRIGger:SOURce TIMer
|
||||||
TRIGger:TIMer 0.5
|
TRIGger:TIMer 0.5
|
||||||
|
|
||||||
|
@ -4,13 +4,21 @@ Created on Fri Jan 24 16:46:06 2025
|
|||||||
|
|
||||||
@author: CPD
|
@author: CPD
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import watchdog
|
|
||||||
from watchdog.observers import Observer
|
from watchdog.observers import Observer
|
||||||
from watchdog.events import LoggingEventHandler, FileSystemEventHandler
|
from watchdog.events import FileSystemEventHandler
|
||||||
import os
|
|
||||||
|
|
||||||
|
class InvalidScript(Exception):
|
||||||
|
def __init__(self, lineNr, message, fix=""):
|
||||||
|
self.lineNr = lineNr
|
||||||
|
self.message = message
|
||||||
|
self.fix = fix
|
||||||
|
self.full_message = f"Line {lineNr}: {message} {fix}"
|
||||||
|
super().__init__(self.full_message)
|
||||||
|
|
||||||
class LedScriptUpdateHandler(FileSystemEventHandler):
|
class LedScriptUpdateHandler(FileSystemEventHandler):
|
||||||
def __init__(self, led_script, verbose=False):
|
def __init__(self, led_script, verbose=False):
|
||||||
@ -30,10 +38,10 @@ class LedScriptUpdateHandler(FileSystemEventHandler):
|
|||||||
|
|
||||||
|
|
||||||
class LedScript:
|
class LedScript:
|
||||||
|
ARRAY_DTYPE = [("dt", "f8"), ("dtsum", "f8"), ("led", "i4"), ("line", "i4")]
|
||||||
def __init__(self, script:np.ndarray|str|int=0, auto_update=False, verbose=False):
|
def __init__(self, script:np.ndarray|str|int=0, auto_update=False, verbose=False):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
script : np.ndarray|str|int
|
script : np.ndarray|str|int
|
||||||
@ -45,48 +53,35 @@ class LedScript:
|
|||||||
The <line> field is the line number if the step in a script and is optional.
|
The <line> field is the line number if the step in a script and is optional.
|
||||||
If str: path to a led script file
|
If str: path to a led script file
|
||||||
If int: constant led state value
|
If int: constant led state value
|
||||||
constantValue : TYPE, optional
|
|
||||||
DESCRIPTION. The default is None.
|
|
||||||
auto_update: bool, optional
|
auto_update: bool, optional
|
||||||
If True and script is a filepath, the script will automatically be reloaded when the file changes
|
If True and script is a filepath, the script will automatically be reloaded when the file changes
|
||||||
|
verbose: bool, optional
|
||||||
|
If True, print messages when important operations occur
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
None.
|
None.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
self.verbose = verbose
|
||||||
self.t_start = 0
|
self.t_start = 0
|
||||||
self.auto_update = False
|
self.auto_update = False
|
||||||
self.filepath = None
|
self.filepath = None
|
||||||
if type(script) == int:
|
if type(script) == int:
|
||||||
self.script = np.array([(0., 0., script)])
|
self.script = np.array([(0., 0., script, 0)], dtype=LedScript.ARRAY_DTYPE)
|
||||||
elif type(script) == np.ndarray:
|
elif type(script) == np.ndarray:
|
||||||
self.script = script
|
self.script = script
|
||||||
elif type(script) == str:
|
elif type(script) == str:
|
||||||
self.script = LedScript.parse_script(script, ignore_errors=False)
|
self.script = LedScript.parse_script(script, ignore_errors=False)
|
||||||
self.filepath = script
|
self.filepath = script
|
||||||
self.auto_update = auto_update
|
self.auto_update = auto_update
|
||||||
if self.auto_update:
|
|
||||||
# event_handler = LoggingEventHandler()
|
|
||||||
event_handler = LedScriptUpdateHandler(self, verbose=True)
|
|
||||||
self.observer = Observer()
|
|
||||||
dirname = os.path.dirname(os.path.abspath(self.filepath)) # directory of the file
|
|
||||||
self.observer.schedule(event_handler, dirname)
|
|
||||||
self.observer.start()
|
|
||||||
if verbose: print(f"Led script is watching for updates on '{self.filepath}'")
|
|
||||||
else:
|
|
||||||
self.observer = None
|
self.observer = None
|
||||||
|
if self.auto_update:
|
||||||
|
self.start_updating()
|
||||||
self.current_dt = 0
|
self.current_dt = 0
|
||||||
assert(self.script.shape[0] > 0)
|
assert(self.script.shape[0] > 0)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.stop()
|
self.stop_updating()
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
print("Led script stopped watching for updates")
|
|
||||||
if self.observer is not None:
|
|
||||||
self.observer.stop()
|
|
||||||
self.observer.join()
|
|
||||||
|
|
||||||
|
|
||||||
def start(self) -> int:
|
def start(self) -> int:
|
||||||
@ -141,16 +136,71 @@ class LedScript:
|
|||||||
return int(self.script["led"][idx])
|
return int(self.script["led"][idx])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_current_index(script, dt:float):
|
def _get_current_index(script, dt:float) -> int:
|
||||||
if script.shape[0] == 1:
|
if script.shape[0] == 1:
|
||||||
return 0
|
return 0
|
||||||
distance = script["dtsum"] - dt
|
distance = script["dtsum"] - dt
|
||||||
idx = np.where(distance >= 0, distance, np.inf).argmin()
|
idx = np.where(distance >= 0, distance, np.inf).argmin()
|
||||||
return idx
|
return idx
|
||||||
|
|
||||||
def get_current_index(self, dt:float):
|
def get_current_index(self, dt:float) -> int:
|
||||||
|
"""
|
||||||
|
Get the index into self.script at `dt`
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
dt : float
|
||||||
|
Time relative to script start.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
int
|
||||||
|
Index into self.script at relative time dt.
|
||||||
|
"""
|
||||||
return LedScript._get_current_index(self.script, dt)
|
return LedScript._get_current_index(self.script, dt)
|
||||||
|
|
||||||
|
def start_updating(self):
|
||||||
|
"""
|
||||||
|
Start watching for updates to the script file.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
ValueError
|
||||||
|
If the LedScript object was initialized with a filepath or if already watching for script updates.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.observer is not None:
|
||||||
|
raise ValueError("Already watching for updates")
|
||||||
|
if self.filepath is None:
|
||||||
|
raise ValueError("Can not watch for updates if the LedScript was not initialized with a file path")
|
||||||
|
# event_handler = LoggingEventHandler()
|
||||||
|
event_handler = LedScriptUpdateHandler(self) #, verbose=True)
|
||||||
|
self.observer = Observer()
|
||||||
|
dirname = os.path.dirname(os.path.abspath(self.filepath)) # directory of the file
|
||||||
|
self.observer.schedule(event_handler, dirname)
|
||||||
|
self.observer.start()
|
||||||
|
if self.verbose: print(f"Led script is watching for updates on '{self.filepath}'")
|
||||||
|
|
||||||
|
|
||||||
|
def stop_updating(self):
|
||||||
|
"""
|
||||||
|
Stop watching for updates to the script file.
|
||||||
|
Does nothing if not currently watching for file updates.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None.
|
||||||
|
"""
|
||||||
|
if self.observer is not None:
|
||||||
|
self.observer.stop()
|
||||||
|
self.observer.join()
|
||||||
|
self.observer = None
|
||||||
|
if self.verbose: print("Led script stopped watching for updates")
|
||||||
|
|
||||||
def update(self, verbose=True):
|
def update(self, verbose=True):
|
||||||
print(f"Updating led script from '{self.filepath}'")
|
print(f"Updating led script from '{self.filepath}'")
|
||||||
newscript = LedScript.parse_script(self.filepath, ignore_errors=False)
|
newscript = LedScript.parse_script(self.filepath, ignore_errors=False)
|
||||||
@ -167,7 +217,31 @@ class LedScript:
|
|||||||
self.script = newscript
|
self.script = newscript
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_script(filepath, ignore_errors=False):
|
def parse_script(filepath: str, ignore_errors:bool=False) -> np.ndarray|tuple[np.ndarray, list[InvalidScript]]:
|
||||||
|
"""
|
||||||
|
Parse a led script from a file into a structured array
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
filepath : str
|
||||||
|
Path to the led script file.
|
||||||
|
ignore_errors : bool, optional
|
||||||
|
If True, does not throw an exception upon script errors.
|
||||||
|
Instead, ignores errornous lines and additionally returns all exceptions in a list.
|
||||||
|
The default is False.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
InvalidScript
|
||||||
|
If encountering invalid lines in the script.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
np.ndarray or tuple[np.ndarray, list[InvalidScript]]
|
||||||
|
Returns the script as structured array. For the format, see the
|
||||||
|
docstring of the LedString constructor.
|
||||||
|
If ignore_errors=True, additionally returns a list of all errors.
|
||||||
|
"""
|
||||||
with open(filepath, "r") as file:
|
with open(filepath, "r") as file:
|
||||||
lines = file.readlines()
|
lines = file.readlines()
|
||||||
|
|
||||||
@ -255,15 +329,8 @@ class LedScript:
|
|||||||
cum_duration = states[-1][1] + duration
|
cum_duration = states[-1][1] + duration
|
||||||
# 6) append
|
# 6) append
|
||||||
states.append((duration, cum_duration, state, i+1))
|
states.append((duration, cum_duration, state, i+1))
|
||||||
states = np.array(states, dtype=[("dt", "f8"), ("dtsum", "f8"), ("led", "i4"), ("line", "i4")])
|
states = np.array(states, dtype=LedScript.ARRAY_DTYPE)
|
||||||
if ignore_errors:
|
if ignore_errors:
|
||||||
return states, errors
|
return states, errors
|
||||||
return states
|
return states
|
||||||
|
|
||||||
class InvalidScript(Exception):
|
|
||||||
def __init__(self, lineNr, message, fix=""):
|
|
||||||
self.lineNr = lineNr
|
|
||||||
self.message = message
|
|
||||||
self.fix = fix
|
|
||||||
self.full_message = f"Line {lineNr}: {message} {fix}"
|
|
||||||
super().__init__(self.full_message)
|
|
@ -24,51 +24,107 @@ def measure(
|
|||||||
data: DataCollector,
|
data: DataCollector,
|
||||||
delta_t: float=0.1,
|
delta_t: float=0.1,
|
||||||
flush_after:int|None=None,
|
flush_after:int|None=None,
|
||||||
|
use_buffer=False,
|
||||||
max_measurements: int=None,
|
max_measurements: int=None,
|
||||||
verbose: bool=False,
|
verbose: bool=False,
|
||||||
pipe: None|Connection=None,
|
command_queue: None|Queue=None,
|
||||||
queue: None|Queue=None
|
data_queue: None|Queue=None
|
||||||
):
|
):
|
||||||
# TODO: find a way to move inherited objects into a process
|
"""
|
||||||
if led_dev is None:
|
Perform a measurement
|
||||||
led_dev = LEDD1B()
|
|
||||||
if vm_dev is None:
|
Parameters
|
||||||
vm_dev = init("GPIB0::22::INSTR")
|
----------
|
||||||
|
vm_dev : VoltageMeasurementDevice
|
||||||
|
DESCRIPTION.
|
||||||
|
led_dev : LedControlDevice
|
||||||
|
DESCRIPTION.
|
||||||
|
led_script : LedScript
|
||||||
|
DESCRIPTION.
|
||||||
|
data : DataCollector
|
||||||
|
DESCRIPTION.
|
||||||
|
delta_t : float, optional
|
||||||
|
Target interval between measurements and led updates. The default is 0.1.
|
||||||
|
flush_after : int|None, optional
|
||||||
|
If int, flush values to disk after <flush_after>. The default is None.
|
||||||
|
use_buffer : TYPE, optional
|
||||||
|
If True, use the buffer measurement mode. The default is False.
|
||||||
|
max_measurements : int, optional
|
||||||
|
Number of measurements to perform before returning.
|
||||||
|
Note: If use_buffer=True, a few more than max_measurements might be performed
|
||||||
|
The default is None.
|
||||||
|
verbose : bool, optional
|
||||||
|
If True, print some messages. The default is False.
|
||||||
|
command_queue : None|Connection, optional
|
||||||
|
A queue to receive to commands from.
|
||||||
|
Commands may be:
|
||||||
|
"stop" -> stops the measurement
|
||||||
|
("led_script", <LedScript object>) a new led script to use
|
||||||
|
The default is None.
|
||||||
|
data_queue : None|Queue, optional
|
||||||
|
A queue to put data in. The default is None.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# old hack when using multiprocessing instead of mulithreading:
|
||||||
|
# devices are not pickleable and thus cant be moved to / shared with the measurement process
|
||||||
|
# if led_dev is None:
|
||||||
|
# led_dev = LEDD1B()
|
||||||
|
# if vm_dev is None:
|
||||||
|
# vm_dev = init("GPIB0::22::INSTR")
|
||||||
# if no "time" in metadata, set the current local time in ISO 8601 format
|
# if no "time" in metadata, set the current local time in ISO 8601 format
|
||||||
# and without microseconds
|
# and without microseconds
|
||||||
if not "time" in data.metadata:
|
if not "time" in data.metadata:
|
||||||
data.metadata["time"] = datetime.datetime.now().replace(microsecond=0).isoformat()
|
data.metadata["time"] = datetime.datetime.now().replace(microsecond=0).isoformat()
|
||||||
data.metadata["test"] = "TEST"
|
data.metadata["test"] = "TEST"
|
||||||
vm_dev.reset(True)
|
vm_dev.reset(True)
|
||||||
|
if use_buffer:
|
||||||
|
vm_dev.buffer_measure(delta_t, verbose=True)
|
||||||
try:
|
try:
|
||||||
i = 0
|
i = 0
|
||||||
led_val = led_script.start()
|
led_val = led_script.start()
|
||||||
t_iter_start = time.time()
|
t_iter_start = time.time()
|
||||||
while max_measurements is None or i < max_measurements:
|
while max_measurements is None or i < max_measurements:
|
||||||
# 1) read value
|
# 1) read value(s)
|
||||||
tval, vval = vm_dev.read_value()
|
if use_buffer:
|
||||||
|
try:
|
||||||
|
values = vm_dev.buffer_read_new_values()
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Error in buffer measurement {i}:", e)
|
||||||
|
values = []
|
||||||
|
else:
|
||||||
|
values = [vm_dev.read_value()]
|
||||||
|
# print(values)
|
||||||
|
# 2) process value(s)
|
||||||
|
for (tval, vval) in values:
|
||||||
if i == 0:
|
if i == 0:
|
||||||
t0 = tval
|
t0 = tval
|
||||||
tval -= t0
|
tval -= t0
|
||||||
current_data = (i, tval, vval, led_val)
|
current_data = (i, tval, vval, led_val)
|
||||||
data.add_data(*current_data)
|
data.add_data(*current_data)
|
||||||
# 2) write data
|
# 3) write data
|
||||||
print(f"n = {i:6d}, t = {tval: .2f} s, U = {vval: .5f} V, LED = {led_val:03}%" + " "*10, end='\r')
|
print(f"n = {i:6d}, t = {tval: .2f} s, U = {vval: .5f} V, LED = {led_val:03}%" + " "*10, end='\r')
|
||||||
if flush_after is not None and (i+1) % flush_after == 0:
|
if flush_after is not None and (i+1) % flush_after == 0:
|
||||||
data.flush(verbose=verbose)
|
data.flush(verbose=verbose)
|
||||||
# if a queue was given, put the data
|
# if a queue was given, put the data
|
||||||
if queue is not None:
|
if data_queue is not None:
|
||||||
queue.put(current_data)
|
data_queue.put(current_data)
|
||||||
|
i += 1
|
||||||
|
|
||||||
# if a pipe was given, check for messages
|
# if a pipe was given, check for messages
|
||||||
if pipe is not None and pipe.poll(0):
|
if command_queue is not None and command_queue.qsize() > 0:
|
||||||
recv = pipe.recv()
|
recv = command_queue.get(block=False)
|
||||||
if recv == "stop":
|
if recv == "stop":
|
||||||
break
|
break
|
||||||
elif type(recv) == tuple and recv[0] == "led_script":
|
elif type(recv) == tuple and recv[0] == "led_script":
|
||||||
led_script = recv[1]
|
led_script = recv[1]
|
||||||
else:
|
else:
|
||||||
print(f"Received invalid message: '{recv}'")
|
print(f"Received invalid message: '{recv}'")
|
||||||
# 3) sleep
|
|
||||||
|
# 4) sleep
|
||||||
# substract the execution time from the sleep time for a more
|
# substract the execution time from the sleep time for a more
|
||||||
# acurate frequency
|
# acurate frequency
|
||||||
dt_sleep = delta_t - (time.time() - t_iter_start)
|
dt_sleep = delta_t - (time.time() - t_iter_start)
|
||||||
@ -76,7 +132,7 @@ def measure(
|
|||||||
# print(f"Sleeping for {dt_sleep}")
|
# print(f"Sleeping for {dt_sleep}")
|
||||||
time.sleep(dt_sleep)
|
time.sleep(dt_sleep)
|
||||||
t_iter_start = time.time()
|
t_iter_start = time.time()
|
||||||
# 4) update LED
|
# 5) update LED
|
||||||
new_led_val = led_script.get_state()
|
new_led_val = led_script.get_state()
|
||||||
if new_led_val != led_val:
|
if new_led_val != led_val:
|
||||||
try:
|
try:
|
||||||
@ -85,10 +141,12 @@ def measure(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error setting led to {new_led_val}%:")
|
print(f"Error setting led to {new_led_val}%:")
|
||||||
print(e)
|
print(e)
|
||||||
i += 1
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
data.flush(verbose=verbose)
|
data.flush(verbose=verbose)
|
||||||
led_dev.off()
|
led_dev.off()
|
||||||
print(data.metadata)
|
print(data.metadata)
|
||||||
print("Measurement stopped" + " "*50)
|
print("Measurement stopped" + " "*50)
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@ class Keithley2700(VoltageMeasurementDevice):
|
|||||||
self.instr = instr
|
self.instr = instr
|
||||||
if check_front_switch:
|
if check_front_switch:
|
||||||
self._check_front_input_selected()
|
self._check_front_input_selected()
|
||||||
|
self.buffer_next_idx = None
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
"""Properly close the instrument connection"""
|
"""Properly close the instrument connection"""
|
||||||
@ -75,6 +76,17 @@ class Keithley2700(VoltageMeasurementDevice):
|
|||||||
raise Exception("The Keithley's INPUT switch must select the [F]ront inputs")
|
raise Exception("The Keithley's INPUT switch must select the [F]ront inputs")
|
||||||
return switch
|
return switch
|
||||||
|
|
||||||
|
|
||||||
|
def query(self, query):
|
||||||
|
try:
|
||||||
|
return self.instr.query(query).strip("\n")
|
||||||
|
except pyvisa.VisaIOError as e:
|
||||||
|
print(f"VisaIOError raised during query: '{query}'")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def query_int(self, query):
|
||||||
|
return int(float(self.query(query)))
|
||||||
|
|
||||||
# RUN COMMANDS ON THE DEVICE
|
# RUN COMMANDS ON THE DEVICE
|
||||||
def run(self, code, verbose=False):
|
def run(self, code, verbose=False):
|
||||||
"""
|
"""
|
||||||
@ -85,10 +97,18 @@ class Keithley2700(VoltageMeasurementDevice):
|
|||||||
code : str
|
code : str
|
||||||
SCPI commands
|
SCPI commands
|
||||||
"""
|
"""
|
||||||
script = '\n'.join([l.strip(" ") for l in code.strip(" ").strip("\n").split("\n") if len(l) > 0 and l[0] not in "#'"])
|
script = ''
|
||||||
|
for line in code.strip(" ").split("\n"):
|
||||||
|
l = line.strip(" ")
|
||||||
|
if len(l) == 0 or l[0] in "#'": continue
|
||||||
|
script += l + "\n"
|
||||||
if verbose:
|
if verbose:
|
||||||
print(f"Running code:\n{script}")
|
print(f"Running code:\n{script}")
|
||||||
|
try:
|
||||||
self.instr.write(script)
|
self.instr.write(script)
|
||||||
|
except pyvisa.VisaIOError as e:
|
||||||
|
print(f"VisaIOError raised while writing command(s):\n'{script}'\n")
|
||||||
|
raise e
|
||||||
|
|
||||||
def run_script(self, script_path, verbose=False):
|
def run_script(self, script_path, verbose=False):
|
||||||
"""
|
"""
|
||||||
@ -118,42 +138,101 @@ class Keithley2700(VoltageMeasurementDevice):
|
|||||||
|
|
||||||
def reset(self, verbose=False):
|
def reset(self, verbose=False):
|
||||||
"""
|
"""
|
||||||
Reset smua and its buffers
|
Reset the device
|
||||||
@param instr : pyvisa instrument
|
|
||||||
"""
|
"""
|
||||||
self.run_script(scripts["instrument_reset"], verbose=verbose)
|
|
||||||
self.buffer_reset()
|
reset_script = """
|
||||||
|
VOLT:DC:RANGe:AUTO ON
|
||||||
|
' DC Voltage measurement
|
||||||
|
SENSe:FUNC 'VOLT:DC'
|
||||||
|
' Set voltage divider if required
|
||||||
|
' SENSE:VOLT:DC:IDIVider OFF
|
||||||
|
' Disable continuous initiation
|
||||||
|
INIT:CONT OFF
|
||||||
|
' Disable Buffer and trigger
|
||||||
|
TRACe:FEED NONE
|
||||||
|
TRACe:FEED:CONTrol NEVer
|
||||||
|
TRIGger:SOURce IMMediate
|
||||||
|
' Set timestamp format to relative
|
||||||
|
SYSTem:TSTamp:TYPE RELative
|
||||||
|
"""
|
||||||
|
self.run(reset_script)
|
||||||
|
# self.run_script(scripts["instrument_reset"], verbose=verbose)
|
||||||
|
self.buffer_clear()
|
||||||
|
|
||||||
|
|
||||||
# INTERACT WITH DEVICE BUFFER
|
# INTERACT WITH DEVICE BUFFER
|
||||||
# might not be needed
|
# might not be needed
|
||||||
def buffer_reset(self):
|
def buffer_clear(self):
|
||||||
buffer_reset = """
|
buffer_reset_script = """
|
||||||
TRACe:CLEar
|
TRACe:CLEar
|
||||||
TRACe:CLEar:AUTO ON
|
TRACe:CLEar:AUTO ON
|
||||||
SYSTem:TSTamp:TYPE RELative
|
|
||||||
"""
|
"""
|
||||||
self.run(buffer_reset)
|
self.run(buffer_reset_script)
|
||||||
|
|
||||||
def buffer_get_size(self, buffer_nr=1):
|
def buffer_get_size(self):
|
||||||
n = self.instr.query("TRACe:POINts?").strip("\n")
|
return self.query_int("TRACe:POINts?")
|
||||||
return int(float(n))
|
|
||||||
|
|
||||||
def buffer_set_size(self, s):
|
def buffer_set_size(self, s):
|
||||||
if not type(s) == int or s < 2 or s > 55000:
|
if not type(s) == int or s < 2 or s > 55000:
|
||||||
raise ValueError(f"Invalid buffer size: {s}. Must be int and between 2 and 55000")
|
raise ValueError(f"Invalid buffer size: {s}. Must be int and between 2 and 55000")
|
||||||
self.instr.write(f"TRACe:POINts {s}")
|
self.instr.write(f"TRACe:POINts {s}")
|
||||||
|
|
||||||
|
def buffer_measure(self, interval: float=0.5, verbose=False):
|
||||||
|
if interval < 0.001 or interval > 999999.999:
|
||||||
|
raise ValueError("Interval must be between 0.001 and 999999.999")
|
||||||
|
self.run(f"""
|
||||||
|
TRIGger:TIMer {interval}
|
||||||
|
TRIGger:COUNt INFinity
|
||||||
|
TRIGger:SOURce TIMer
|
||||||
|
""")
|
||||||
|
self.buffer_clear()
|
||||||
|
self.run("""
|
||||||
|
TRACe:FEED SENSe
|
||||||
|
' write continuously
|
||||||
|
TRACe:FEED:CONTrol ALWays
|
||||||
|
INITiate:CONTinuous ON
|
||||||
|
""")
|
||||||
|
self.buffer_next_idx = 0
|
||||||
|
self.buffer_size = self.buffer_get_size()
|
||||||
|
if verbose: print("Started buffer measurement")
|
||||||
|
|
||||||
|
def buffer_read_new_values(self):
|
||||||
|
if self.buffer_next_idx is None:
|
||||||
|
raise ValueError("You must first start a buffer measurement by calling buffer_measure()")
|
||||||
|
# TRACe:NEXT? returns the next index that will be written to
|
||||||
|
new_next_idx = self.query_int("TRACe:NEXT?")
|
||||||
|
if new_next_idx == self.buffer_next_idx:
|
||||||
|
raise ValueError(f"No new value or buffer has been filled completely (next reading at {self.buffer_next_idx})")
|
||||||
|
# if reached end of buffer, first read to end and then from beginning
|
||||||
|
vals = ""
|
||||||
|
if new_next_idx < self.buffer_next_idx:
|
||||||
|
count = self.buffer_size - self.buffer_next_idx
|
||||||
|
# print(f"start={self.buffer_next_idx}, stop={new_next_idx}, count={count}")
|
||||||
|
vals += self.query(f"TRACe:DATA:SELected? {self.buffer_next_idx}, {count}").strip("\n")
|
||||||
|
self.buffer_next_idx = 0
|
||||||
|
count = new_next_idx - self.buffer_next_idx
|
||||||
|
if count > 0:
|
||||||
|
# print(f"start={self.buffer_next_idx}, stop={new_next_idx}, count={count}")
|
||||||
|
vals += self.query(f"TRACe:DATA:SELected? {self.buffer_next_idx}, {count}").strip("\n")
|
||||||
|
self.buffer_next_idx = new_next_idx
|
||||||
|
processed_vals = [self.process_reading(val) for val in vals.strip("#").split("#")]
|
||||||
|
return processed_vals
|
||||||
|
|
||||||
|
|
||||||
# MEASUREMENT
|
# MEASUREMENT
|
||||||
def process_reading(self, reading: str):
|
@staticmethod
|
||||||
|
def process_reading(reading: str):
|
||||||
"""
|
"""
|
||||||
process a reading. Only works with VDC and relative time stamps right now!
|
process a reading. Only works with VDC and relative time stamps right now!
|
||||||
'-1.19655066E+01VDC,+9627.275SECS,+64993RDNG#\n'
|
'-1.19655066E+01VDC,+9627.275SECS,+64993RDNG\n'
|
||||||
|
May have trailing commas and trailing '#' characters
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
[timestamp, voltage]
|
[timestamp, voltage]
|
||||||
"""
|
"""
|
||||||
parts = reading.split(",")
|
parts = reading.strip("#").strip(",").split(",")
|
||||||
if len(parts) != 3:
|
if len(parts) != 3:
|
||||||
raise ValueError(f"Invalid reading: '{reading}'")
|
raise ValueError(f"Invalid reading: '{reading}'")
|
||||||
vdc = float(parts[0][:-3])
|
vdc = float(parts[0][:-3])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user