From c0158e24781ff29fec7d18508807bb54264fd8f0 Mon Sep 17 00:00:00 2001 From: CPD Date: Tue, 21 Jan 2025 18:43:07 +0100 Subject: [PATCH] use interfaces --- .../arduino-thorlabs-ledd1b.ino | 1 - cpdctrl/backends/__init__.py | 0 cpdctrl/backends/keithley/__init__.py | 0 cpdctrl/cpdctrl-interactive.py | 158 +++++------------- cpdctrl/{led => led_control}/__init__.py | 0 cpdctrl/led_control/base.py | 46 +++++ .../impl}/thorlabs_ledd1b.py | 8 +- cpdctrl/update_funcs.py | 2 +- cpdctrl/utility/data.py | 20 +++ cpdctrl/voltage_measurement/base.py | 53 ++++++ .../impl/keithley2700.py} | 149 ++++++++--------- 11 files changed, 236 insertions(+), 201 deletions(-) delete mode 100644 cpdctrl/backends/__init__.py delete mode 100644 cpdctrl/backends/keithley/__init__.py rename cpdctrl/{led => led_control}/__init__.py (100%) create mode 100644 cpdctrl/led_control/base.py rename cpdctrl/{led => led_control/impl}/thorlabs_ledd1b.py (88%) create mode 100644 cpdctrl/voltage_measurement/base.py rename cpdctrl/{backends/keithley/keithley.py => voltage_measurement/impl/keithley2700.py} (54%) diff --git a/arduino-thorlabs-ledd1b/arduino-thorlabs-ledd1b.ino b/arduino-thorlabs-ledd1b/arduino-thorlabs-ledd1b.ino index ab80318..a683794 100644 --- a/arduino-thorlabs-ledd1b/arduino-thorlabs-ledd1b.ino +++ b/arduino-thorlabs-ledd1b/arduino-thorlabs-ledd1b.ino @@ -46,7 +46,6 @@ void loop() { default: blink_internal_led(5); break; - } } void blink_internal_led(unsigned N) { diff --git a/cpdctrl/backends/__init__.py b/cpdctrl/backends/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/cpdctrl/backends/keithley/__init__.py b/cpdctrl/backends/keithley/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/cpdctrl/cpdctrl-interactive.py b/cpdctrl/cpdctrl-interactive.py index 2759e00..9213d86 100644 --- a/cpdctrl/cpdctrl-interactive.py +++ b/cpdctrl/cpdctrl-interactive.py @@ -6,6 +6,7 @@ always records iv-t curves i-data -> smua.nvbuffer1 v-data -> smua.nvbuffer2 """ +version = "0.1" import numpy as np import matplotlib.pyplot as plt @@ -21,7 +22,6 @@ import atexit import argparse - if __name__ == "__main__": import sys if __package__ is None: @@ -34,35 +34,26 @@ if __name__ == "__main__": prog="cpdctrl", description="measure voltage using a Keithley SMU", ) - backend_group = parser.add_mutually_exclusive_group(required=True) + backend_group = parser.add_mutually_exclusive_group(required=False) backend_group.add_argument("-k", "--keithley", action="store_true") - backend_group.add_argument("-t", "--testing", action='store_true') + # backend_group.add_argument("-t", "--testing", action='store_true') parser.add_argument("-c", "--config", action="store", help="alternate path to config file") args = vars(parser.parse_args()) - i = 1 - while i < len(sys.argv): - if args["keithley"]: - import cpdctrl.backends.keithley.keithley as _backend - # import cpdctrl.backends.keithley.measure as _measure - elif args["testing"]: - import cpdctrl.backends.testing.testing as _backend - import cpdctrl.backends.testing.measure as _measure - elif sys.argv[i] in ["-c", "--config"]: - if i+1 < len(sys.argv): - config_path = sys.argv[i+1] - else: - print("-c requires an extra argument: path of config file") - i += 1 - i += 1 +from .voltage_measurement.base import VoltageMeasurementDevice as VDev +from .voltage_measurement.impl import keithley2700 as _volt +from .led_control.impl import thorlabs_ledd1b as _led + +from .utility.data import DataCollector -from cpdctrl.utility import data as _data -from cpdctrl.utility.data import load_dataframe -from cpdctrl.utility import file_io -from cpdctrl.update_funcs import _Monitor, _update_print -config_path = path.expanduser("~/.config/m-teng.json") +from .utility import data as _data +from .utility.data import load_dataframe +from .utility import file_io +from .update_funcs import _Monitor, _update_print + +config_path = path.expanduser("~/.config/cpdctrl.json") _runtime_vars = { "last-measurement": "" @@ -78,63 +69,9 @@ settings = { test = False # global variable for the instrument/client returned by pyvisa/bleak -dev = None - - -def monitor_count(count=5000, interval=None, max_points_shown=160): - """ - Take measurements in and monitor live with matplotlib. - - @details: - - Resets the buffers - - Opens a matplotlib window and takes measurements depending on settings["interval"] - Uses the device internal overlappedY measurement method, which allows for greater precision - You can take the data from the buffer afterwards, using save_csv - @param count: count - @param interval: interval, defaults to settings["interval"] - @param max_points_shown: how many points should be shown at once. None means infinite - """ - if not interval: interval = settings["interval"] - plt_monitor = _Monitor(max_points_shown, use_print=True) - update_func = plt_monitor.update - - print(f"Starting measurement with:\n\tinterval = {interval}s\nSave the data using 'save_csv()' afterwards.") - try: - _measure.measure_count(dev, count=count, interval=interval, beep_done=False, verbose=False, update_func=update_func, update_interval=0.05) - except KeyboardInterrupt: - if args["keithley"]: - dev.write(f"smua.source.output = smua.OUTPUT_OFF") - print("Monitoring cancelled, measurement might still continue" + " "*50) - else: - print("Measurement finished" + " "*50) - -def measure_count(count=5000, interval=None): - """ - Take measurements in - - @details: - - Resets the buffers - - Takes measurements depending on settings["interval"] - Uses the device internal overlappedY measurement method, which allows for greater precision - You can take the data from the buffer afterwards, using save_csv - @param count: count - @param interval: interval, defaults to settings["interval"] - """ - if not interval: interval = settings["interval"] - update_func = _update_print - - print(f"Starting measurement with:\n\tinterval = {interval}s\nSave the data using 'save_csv()' afterwards.") - try: - _measure.measure_count(dev, count=count, interval=interval, beep_done=False, verbose=False, update_func=update_func, update_interval=0.05) - except KeyboardInterrupt: - if args["keithley"]: - dev.write(f"smua.source.output = smua.OUTPUT_OFF") - print("Monitoring cancelled, measurement might still continue" + " "*50) - else: - print("Measurement finished" + " "*50) - - - +dev:VDev|None = None +data = DataCollector(settings["datadir"], settings["name"]) +t0 = 0 def monitor(interval=None, max_measurements=None, max_points_shown=160): """ @@ -154,10 +91,17 @@ def monitor(interval=None, max_measurements=None, max_points_shown=160): if not interval: interval = settings["interval"] print(f"Starting measurement with:\n\tinterval = {interval}s\nUse to stop. Save the data using 'save_csv()' afterwards.") plt_monitor = _Monitor(use_print=True, max_points_shown=max_points_shown) - update_func = plt_monitor.update + data.clear() + def update_func(i, t, v): + global t0 + if i == 0: + t0 = t + t -= t0 + data.add_data((t, v)) + plt_monitor.update(i, t, v) + # update_led() dev.measure(interval=interval, max_measurements=max_measurements, update_func=update_func) - def measure(interval=None, max_measurements=None): """ Measure voltages @@ -176,20 +120,7 @@ def measure(interval=None, max_measurements=None): print(f"Starting measurement with:\n\tinterval = {interval}s\nUse to stop. Save the data using 'save_csv()' afterwards.") update_func = _update_print dev.measure(interval=interval, max_measurements=max_measurements, update_func=update_func) - - -def get_dataframe(): - """ - Get a pandas dataframe from the data in smua.nvbuffer1 and smua.nvbuffer2 - """ - global k, settings, _runtime_vars - ibuffer = _backend.collect_buffer(dev, 1) - vbuffer = _backend.collect_buffer(dev, 2) - df = _data.buffers2dataframe(ibuffer, vbuffer) - df.basename = file_io.get_next_filename(settings["name"], settings["datadir"]) - df.name = f"{df.basename} @ {_runtime_vars['last-measurement']}" - return df - + def save_csv(): """ @@ -281,13 +212,13 @@ Run 'help("topic")' to see more information on a topic""") Functions: name("") - short for set("name", "") set("setting", value) - set a setting to a value - save_settings() - store the settings as "m-teng.json" in the working directory + save_settings() - store the settings as "cpdctrl.json" in the working directory load_settings() - load settings from a file The global variable 'config_path' determines the path used by save/load_settings. Use -c '' to set another path. The serach path is: - /m-teng.json - $XDG_CONFIG_HOME/m-teng.json - ~/.config/m-teng.json + /cpdctrl.json + $XDG_CONFIG_HOME/cpdctrl.json + ~/.config/cpdctrl.json """) elif topic == "imports": print("""Imports: @@ -309,23 +240,23 @@ Functions: def init(): global dev, settings, config_path - print(r""" ______________________ _______ ________ - _____ \__ ___/\_ _____/ \ \ / _____/ - / \ ______| | | __)_ / | \ / \ ___ -| Y Y \/_____/| | | \/ | \\ \_\ \ -|__|_| / |____| /_______ /\____|__ / \______ / - \/ \/ \/ \/ 1.2 -Interactive Shell for TENG measurements with Keithley 2600B + print(r""" .___ __ .__ + ____ ______ __| _/_____/ |________| | +_/ ___\\____ \ / __ |/ ___\ __\_ __ \ | +\ \___| |_> > /_/ \ \___| | | | \/ |__ + \___ > __/\____ |\___ >__| |__| |____/ + \/|__| \/ \/ """ + f"""{version} +Interactive Shell for CPD measurements with Keithley 2700B --- Enter 'help()' for a list of commands""") from os import environ - if path.isfile("m-teng.json"): - config_path = "m-teng.json" + if path.isfile("cpdctrl.json"): + config_path = "cpdctrl.json" elif 'XDG_CONFIG_HOME' in environ.keys(): - # and path.isfile(environ["XDG_CONFIG_HOME"] + "/m-teng.json"): - config_path = environ["XDG_CONFIG_HOME"] + "/m-teng.json" + # and path.isfile(environ["XDG_CONFIG_HOME"] + "/cpdctrl.json"): + config_path = environ["XDG_CONFIG_HOME"] + "/cpdctrl.json" else: - config_path = path.expanduser("~/.config/m-teng.json") + config_path = path.expanduser("~/.config/cpdctrl.json") if args["config"]: config_path = args["config"] @@ -340,7 +271,8 @@ Enter 'help()' for a list of commands""") makedirs(settings["datadir"]) try: - dev = _backend.init() + dev = _volt.init() + led = _led.LEDD1B() except Exception as e: print(e) exit(1) diff --git a/cpdctrl/led/__init__.py b/cpdctrl/led_control/__init__.py similarity index 100% rename from cpdctrl/led/__init__.py rename to cpdctrl/led_control/__init__.py diff --git a/cpdctrl/led_control/base.py b/cpdctrl/led_control/base.py new file mode 100644 index 0000000..0f7b105 --- /dev/null +++ b/cpdctrl/led_control/base.py @@ -0,0 +1,46 @@ +from abc import ABC, abstractmethod +from typing import Callable +""" +Created on Tue Jan 21 16:26:13 2025 + +@author: Matthias Quintern +""" + +class LedControlDevice(ABC): + @abstractmethod + def on(self): + """ + Set the led brightness to 100% + + Returns + ------- + None. + """ + pass + + @abstractmethod + def off(self): + """ + Set the led brightness to 0% + + Returns + ------- + None. + """ + pass + + # @abstractmethod + def set_level(level:int): + """ + Set the led brightness to a certain level + + Parameters + ---------- + level : int + Brightness level in percent. + + Returns + ------- + None. + """ + pass \ No newline at end of file diff --git a/cpdctrl/led/thorlabs_ledd1b.py b/cpdctrl/led_control/impl/thorlabs_ledd1b.py similarity index 88% rename from cpdctrl/led/thorlabs_ledd1b.py rename to cpdctrl/led_control/impl/thorlabs_ledd1b.py index 91e0ec1..c662fbe 100644 --- a/cpdctrl/led/thorlabs_ledd1b.py +++ b/cpdctrl/led_control/impl/thorlabs_ledd1b.py @@ -1,9 +1,11 @@ import serial -class LEDD1B: +from ..base import LedControlDevice + +class LEDD1B(LedControlDevice): def __init__(self, port="COM4"): self.arduino = serial.Serial(port=port, baudrate=9600, timeout=.1) - self._check_arduino_software() + # self._check_arduino_software() def __del__(self): self.arduino.close() @@ -32,4 +34,4 @@ class LEDD1B: self._write("0") if __name__ == '__main__': - led = LEDD1B() + led = LEDD1B() \ No newline at end of file diff --git a/cpdctrl/update_funcs.py b/cpdctrl/update_funcs.py index 8f66141..d964f1e 100644 --- a/cpdctrl/update_funcs.py +++ b/cpdctrl/update_funcs.py @@ -6,7 +6,7 @@ def _update_print(i, tval, vval): class _Monitor: """ - Monitor v and i data + Monitor v and i data in a matplotlib window """ def __init__(self, max_points_shown=None, use_print=False): self.max_points_shown = max_points_shown diff --git a/cpdctrl/utility/data.py b/cpdctrl/utility/data.py index 1b82ae2..6b56da4 100644 --- a/cpdctrl/utility/data.py +++ b/cpdctrl/utility/data.py @@ -3,6 +3,26 @@ import numpy as np from os import path import matplotlib.pyplot as plt +class DataCollector: + def __init__(self, data_name, data_path): + self.data = [] + self.name = data_name + self.path = data_path + + def clear(self): + self.data = [] + + def add_data(self, data): + self.data.append(data) + + def to_dataframe(self): + return pd.DataFrame(self.data) + + def save_csv(self): + self.to_dataframe().to_csv(path.join(self.path, self.name + ".csv"), index=False, header=True) + + + # deprecated # def buffer2dataframe(buffer): # df = pd.DataFrame(buffer) diff --git a/cpdctrl/voltage_measurement/base.py b/cpdctrl/voltage_measurement/base.py new file mode 100644 index 0000000..e90809e --- /dev/null +++ b/cpdctrl/voltage_measurement/base.py @@ -0,0 +1,53 @@ +from abc import ABC, abstractmethod +from typing import Callable + +""" +Created on Tue Jan 21 16:19:01 2025 + +@author: Matthias Quintern +""" + +class VoltageMeasurementDevice(ABC): + # RUN COMMANDS ON THE DEVICE + @abstractmethod + def run(self, code, verbose=False): + pass + @abstractmethod + def run_script(self, script_path, verbose=False): + pass + + @abstractmethod + def reset(self, verbose=False): + pass + + @abstractmethod + def read_value(self) -> tuple[float, float]: + """ + Read a single value + + Returns + ------- + [timestamp, voltage] + """ + pass + + @abstractmethod + def measure(self, interval: int, update_func: Callable[None, [int, float, float]]|None=None, max_measurements:int|None=None): + """ + Take voltage readings after milliseconds. + + Parameters + ---------- + interval : int + Number of milliseconds to wait between readings. + update_func : Callable[None, [int, float, float]] or None, optional + A function that is called after each reading with parameters ,