From 2631898c35ccb4ca364c685dbffedc86c47589b1 Mon Sep 17 00:00:00 2001 From: CPD Date: Thu, 13 Feb 2025 12:16:03 +0100 Subject: [PATCH] Add config file managing class --- cpdctrl/cpdctrl-interactive.py | 80 +++++++++++++++++++--------------- cpdctrl/utility/config_file.py | 33 ++++++++++++++ 2 files changed, 79 insertions(+), 34 deletions(-) create mode 100644 cpdctrl/utility/config_file.py diff --git a/cpdctrl/cpdctrl-interactive.py b/cpdctrl/cpdctrl-interactive.py index acf2b0e..9c19d0d 100644 --- a/cpdctrl/cpdctrl-interactive.py +++ b/cpdctrl/cpdctrl-interactive.py @@ -53,26 +53,31 @@ from .led_script import LedScript from .measurement import measure as _measure from .utility.data import DataCollector from .utility.data import plot_cpd_data as data_plot +from .utility.config_file import ConfigFile from .utility import file_io from .update_funcs import _Monitor, _update_print -config_path = path.expanduser("~/.config/cpdctrl.json") - +# CONFIGURATION _runtime_vars = { "last-measurement": "" } - +# defaults, these may be overridden by a config file settings = { "datadir": path.expanduser("~/data"), "name": "interactive-test", - "led": "unkown", + "led": "unknown", "interval": 0.5, "flush_after": 3000, "use_buffer": False, } +cfilename: str = "cpdctrl.yaml" +config_path: str = "" +config_file: ConfigFile = ConfigFile("") + test = False +# DEVICES # global variable for the instrument/client returned by pyvisa/bleak dev: VoltageMeasurementDevice|None = None led: LedControlDevice|None = None @@ -81,7 +86,7 @@ t0 = 0 data = None md = None -def monitor(script: str|int=0, interval: float|None=None, metadata:dict={}, flush_after: int|None=None, use_buffer: bool|None=None, max_measurements=None, max_points_shown=None): +def monitor(script: str|int=0, interval: float|None=None, metadata:dict={}, flush_after: int|None=None, use_buffer: bool|None=None, max_measurements=None, stop_on_script_end: bool=False, max_points_shown=None): """ Monitor the voltage with matplotlib. - Opens a matplotlib window and takes measurements depending on settings["interval"] @@ -96,7 +101,7 @@ def monitor(script: str|int=0, interval: float|None=None, metadata:dict={}, flus Parameters ---------- script : str|int - Path to a led script file or a constant value between 0 and 100 for the LED. + Path to a led script file, or a constant value between 0 and 100 for the LED. interval : float|None Time between measurements. If None, the value is taken from the settings. @@ -106,12 +111,15 @@ def monitor(script: str|int=0, interval: float|None=None, metadata:dict={}, flus flush_after : int|None Flush the data to disk after readings If None, the value is taken from the settings. - use_buffer : Bool + use_buffer : bool If True, use the voltage measurement device's internal buffer for readings, which leads to more accurate timings. If None, the value is taken from the settings. max_points_shown : int|None how many points should be shown at once. None means infinite - max_measurements : maximum number of measurements. None means infinite + max_measurements : int|None + maximum number of measurements. None means infinite + stop_on_script_end : bool, optional + Stop measurement when the script end is reached """ global _runtime_vars, data_collector, dev, led global data, md @@ -140,7 +148,8 @@ def monitor(script: str|int=0, interval: float|None=None, metadata:dict={}, flus interval, flush_after, use_buffer, - max_measurements, + max_measurements, + stop_on_script_end, False, # verbose command_queue, data_queue @@ -158,6 +167,7 @@ def monitor(script: str|int=0, interval: float|None=None, metadata:dict={}, flus pass command_queue.put("stop") proc_measure.join() + print("Measurement stopped" + " "*50) led_script.stop_updating() # stop watching for file updates (if enabled) data_collector.save_csv(verbose=True) data, metadata = data_collector.get_data() @@ -165,8 +175,7 @@ def monitor(script: str|int=0, interval: float|None=None, metadata:dict={}, flus plt.ioff() fig_path = path.join(data_collector.path, data_collector.dirname + ".pdf") fig.savefig(fig_path) - - + # DATA def data_load(dirname:str) -> tuple[np.ndarray, dict]: @@ -186,8 +195,6 @@ def data_load(dirname:str) -> tuple[np.ndarray, dict]: dirpath = path.join(settings["datadir"], dirname) data, md = DataCollector.load_data(dirpath, verbose=True) -# data_plot imported - # SETTINGS def set(setting, value): global settings, config_path @@ -196,19 +203,20 @@ def set(setting, value): print(f"set: setting '{setting}' currently holds a value of type '{type(settings[setting])}'") return settings[setting] = value + config_file.set(setting, value) def name(s:str): global settings settings["name"] = s def save_settings(): - with open(config_path, "w") as file: - json.dump(settings, file, indent=4) + global settings + config_file.set_values(settings) + config_file.save() def load_settings(): global settings, config_path - with open(config_path, "r") as file: - settings = json.load(file) + settings = config_file.get_values() settings["datadir"] = path.expanduser(settings["datadir"]) # replace ~ def help(topic=None): @@ -226,23 +234,29 @@ Available topics: Run 'help("topic")' to see more information on a topic""") elif topic in [settings, "settings"]: - print("""Settings: + print(f"""Settings: name: str - name of the measurement, determines filename led: str - name/model of the LED that is being used datadir: str - output directory for the csv files interval: int - interval (inverse frequency) of the measurements, in seconds - beep: bool - wether the device should beep or not + beep: bool - whether the device should beep or not + Functions: name("") - short for set("name", "") set("setting", value) - set a setting to a value 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: - /cpdctrl.json - $XDG_CONFIG_HOME/cpdctrl.json - ~/.config/cpdctrl.json + +Upon startup, settings are loaded from the config file. +The global variable 'config_path' determines the path used by save/load_settings. Use -c '' to set another path. +The search path is: + /{cfilename} + $XDG_CONFIG_HOME/{cfilename} + ~/.config/cpdctrl/{cfilename} + +The current file path is: + {config_path} """) elif topic == "imports": print("""Imports: @@ -255,7 +269,7 @@ Functions: def init(): - global dev, led, settings, config_path + global dev, led, settings, config_path, config_file print(r""" .___ __ .__ ____ ______ __| _/_____/ |________| | _/ ___\\____ \ / __ |/ ___\ __\_ __ \ | @@ -266,21 +280,19 @@ Interactive Shell for CPD measurements with Keithley 2700B --- Enter 'help()' for a list of commands""") from os import environ - if path.isfile("cpdctrl.json"): - config_path = "cpdctrl.json" + + if path.isfile(cfilename): + config_path = cfilename elif 'XDG_CONFIG_HOME' in environ.keys(): # and path.isfile(environ["XDG_CONFIG_HOME"] + "/cpdctrl.json"): - config_path = environ["XDG_CONFIG_HOME"] + "/cpdctrl.json" + config_path = path.join(environ["XDG_CONFIG_HOME"], "cpdctrl", cfilename) else: - config_path = path.expanduser("~/.config/cpdctrl.json") + config_path = path.join(path.expanduser("~/.config/cpdctrl"), cfilename) if args["config"]: config_path = args["config"] - if not path.isdir(path.dirname(config_path)): - makedirs(path.dirname(config_path)) - - if path.isfile(config_path): - load_settings() + config_file = ConfigFile(config_path, init_values=settings) + load_settings() if not path.isdir(settings["datadir"]): makedirs(settings["datadir"]) diff --git a/cpdctrl/utility/config_file.py b/cpdctrl/utility/config_file.py new file mode 100644 index 0000000..bad300c --- /dev/null +++ b/cpdctrl/utility/config_file.py @@ -0,0 +1,33 @@ +from os import environ, makedirs, path +import yaml + +class ConfigFile: + def __init__(self, filepath: str, init_values = None): + self.values = {} + if init_values: + self.values = init_values + self.filepath = filepath + if path.isfile(self.filepath): + with open(self.filepath, "r") as file: + self.values |= yaml.safe_load(file) + + def save(self): + if not self.filepath: return + directory = path.dirname(self.filepath) + if not path.isdir(directory): + makedirs(directory) + with open(self.filepath, "w") as file: + yaml.dump(self.values, file) + + def get(self, name: str, default=None): + if name in self.values: return self.values[name] + return default + + def set(self, name: str, value): + self.values[name] = value + + def get_values(self): + return self.values.copy() + + def set_values(self, values): + self.values = values \ No newline at end of file