Add config file managing class

This commit is contained in:
CPD 2025-02-13 12:16:03 +01:00
parent 154235dbe5
commit 2631898c35
2 changed files with 79 additions and 34 deletions

View File

@ -53,26 +53,31 @@ from .led_script import LedScript
from .measurement import measure as _measure from .measurement import measure as _measure
from .utility.data import DataCollector from .utility.data import DataCollector
from .utility.data import plot_cpd_data as data_plot from .utility.data import plot_cpd_data as data_plot
from .utility.config_file import ConfigFile
from .utility import file_io from .utility import file_io
from .update_funcs import _Monitor, _update_print from .update_funcs import _Monitor, _update_print
config_path = path.expanduser("~/.config/cpdctrl.json") # CONFIGURATION
_runtime_vars = { _runtime_vars = {
"last-measurement": "" "last-measurement": ""
} }
# defaults, these may be overridden by a config file
settings = { settings = {
"datadir": path.expanduser("~/data"), "datadir": path.expanduser("~/data"),
"name": "interactive-test", "name": "interactive-test",
"led": "unkown", "led": "unknown",
"interval": 0.5, "interval": 0.5,
"flush_after": 3000, "flush_after": 3000,
"use_buffer": False, "use_buffer": False,
} }
cfilename: str = "cpdctrl.yaml"
config_path: str = ""
config_file: ConfigFile = ConfigFile("")
test = False test = False
# DEVICES
# global variable for the instrument/client returned by pyvisa/bleak # global variable for the instrument/client returned by pyvisa/bleak
dev: VoltageMeasurementDevice|None = None dev: VoltageMeasurementDevice|None = None
led: LedControlDevice|None = None led: LedControlDevice|None = None
@ -81,7 +86,7 @@ t0 = 0
data = None data = None
md = 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. Monitor the voltage with matplotlib.
- Opens a matplotlib window and takes measurements depending on settings["interval"] - 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 Parameters
---------- ----------
script : str|int 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 interval : float|None
Time between measurements. Time between measurements.
If None, the value is taken from the settings. 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_after : int|None
Flush the data to disk after <flush_after> readings Flush the data to disk after <flush_after> readings
If None, the value is taken from the settings. 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 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. If None, the value is taken from the settings.
max_points_shown : int|None max_points_shown : int|None
how many points should be shown at once. None means infinite 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 _runtime_vars, data_collector, dev, led
global data, md global data, md
@ -140,7 +148,8 @@ def monitor(script: str|int=0, interval: float|None=None, metadata:dict={}, flus
interval, interval,
flush_after, flush_after,
use_buffer, use_buffer,
max_measurements, max_measurements,
stop_on_script_end,
False, # verbose False, # verbose
command_queue, command_queue,
data_queue data_queue
@ -158,6 +167,7 @@ def monitor(script: str|int=0, interval: float|None=None, metadata:dict={}, flus
pass pass
command_queue.put("stop") command_queue.put("stop")
proc_measure.join() proc_measure.join()
print("Measurement stopped" + " "*50)
led_script.stop_updating() # stop watching for file updates (if enabled) led_script.stop_updating() # stop watching for file updates (if enabled)
data_collector.save_csv(verbose=True) data_collector.save_csv(verbose=True)
data, metadata = data_collector.get_data() 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() plt.ioff()
fig_path = path.join(data_collector.path, data_collector.dirname + ".pdf") fig_path = path.join(data_collector.path, data_collector.dirname + ".pdf")
fig.savefig(fig_path) fig.savefig(fig_path)
# DATA # DATA
def data_load(dirname:str) -> tuple[np.ndarray, dict]: 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) dirpath = path.join(settings["datadir"], dirname)
data, md = DataCollector.load_data(dirpath, verbose=True) data, md = DataCollector.load_data(dirpath, verbose=True)
# data_plot imported
# SETTINGS # SETTINGS
def set(setting, value): def set(setting, value):
global settings, config_path 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])}'") print(f"set: setting '{setting}' currently holds a value of type '{type(settings[setting])}'")
return return
settings[setting] = value settings[setting] = value
config_file.set(setting, value)
def name(s:str): def name(s:str):
global settings global settings
settings["name"] = s settings["name"] = s
def save_settings(): def save_settings():
with open(config_path, "w") as file: global settings
json.dump(settings, file, indent=4) config_file.set_values(settings)
config_file.save()
def load_settings(): def load_settings():
global settings, config_path global settings, config_path
with open(config_path, "r") as file: settings = config_file.get_values()
settings = json.load(file)
settings["datadir"] = path.expanduser(settings["datadir"]) # replace ~ settings["datadir"] = path.expanduser(settings["datadir"]) # replace ~
def help(topic=None): def help(topic=None):
@ -226,23 +234,29 @@ Available topics:
Run 'help("topic")' to see more information on a topic""") Run 'help("topic")' to see more information on a topic""")
elif topic in [settings, "settings"]: elif topic in [settings, "settings"]:
print("""Settings: print(f"""Settings:
name: str - name of the measurement, determines filename name: str - name of the measurement, determines filename
led: str - name/model of the LED that is being used led: str - name/model of the LED that is being used
datadir: str - output directory for the csv files datadir: str - output directory for the csv files
interval: int - interval (inverse frequency) of the measurements, in seconds 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: Functions:
name("<name>") - short for set("name", "<name>") name("<name>") - short for set("name", "<name>")
set("setting", value) - set a setting to a value set("setting", value) - set a setting to a value
save_settings() - store the settings as "cpdctrl.json" in the working directory save_settings() - store the settings as "cpdctrl.json" in the working directory
load_settings() - load settings from a file load_settings() - load settings from a file
The global variable 'config_path' determines the path used by save/load_settings. Use -c '<path>' to set another path.
The serach path is: Upon startup, settings are loaded from the config file.
<working-dir>/cpdctrl.json The global variable 'config_path' determines the path used by save/load_settings. Use -c '<path>' to set another path.
$XDG_CONFIG_HOME/cpdctrl.json The search path is:
~/.config/cpdctrl.json <working-dir>/{cfilename}
$XDG_CONFIG_HOME/{cfilename}
~/.config/cpdctrl/{cfilename}
The current file path is:
{config_path}
""") """)
elif topic == "imports": elif topic == "imports":
print("""Imports: print("""Imports:
@ -255,7 +269,7 @@ Functions:
def init(): def init():
global dev, led, settings, config_path global dev, led, settings, config_path, config_file
print(r""" .___ __ .__ print(r""" .___ __ .__
____ ______ __| _/_____/ |________| | ____ ______ __| _/_____/ |________| |
_/ ___\\____ \ / __ |/ ___\ __\_ __ \ | _/ ___\\____ \ / __ |/ ___\ __\_ __ \ |
@ -266,21 +280,19 @@ Interactive Shell for CPD measurements with Keithley 2700B
--- ---
Enter 'help()' for a list of commands""") Enter 'help()' for a list of commands""")
from os import environ 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(): elif 'XDG_CONFIG_HOME' in environ.keys():
# and path.isfile(environ["XDG_CONFIG_HOME"] + "/cpdctrl.json"): # 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: else:
config_path = path.expanduser("~/.config/cpdctrl.json") config_path = path.join(path.expanduser("~/.config/cpdctrl"), cfilename)
if args["config"]: if args["config"]:
config_path = args["config"] config_path = args["config"]
if not path.isdir(path.dirname(config_path)): config_file = ConfigFile(config_path, init_values=settings)
makedirs(path.dirname(config_path)) load_settings()
if path.isfile(config_path):
load_settings()
if not path.isdir(settings["datadir"]): if not path.isdir(settings["datadir"]):
makedirs(settings["datadir"]) makedirs(settings["datadir"])

View File

@ -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