diff --git a/cpdctrl_gui/main.py b/cpdctrl_gui/main.py index 07e5e6f..f04554c 100644 --- a/cpdctrl_gui/main.py +++ b/cpdctrl_gui/main.py @@ -1,3 +1,4 @@ +import os import sys if __name__ == "__main__": if __package__ is None: @@ -14,5 +15,4 @@ from cpdctrl_gui import init if __name__ == '__main__': import sys - - sys.exit(init.run()) + sys.exit(init.run()) \ No newline at end of file diff --git a/cpdctrl_gui/resources/about.md b/cpdctrl_gui/resources/about.md index e2efd25..b520443 100644 --- a/cpdctrl_gui/resources/about.md +++ b/cpdctrl_gui/resources/about.md @@ -1,4 +1,5 @@ ## About +`cpdctrl-gui` is a program for conducting contact potential difference (CPD) measurements under illumination. - Author: Matthias Quintern - License: [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html) diff --git a/cpdctrl_gui/resources/data_recording.md b/cpdctrl_gui/resources/data_recording.md new file mode 100644 index 0000000..874dab6 --- /dev/null +++ b/cpdctrl_gui/resources/data_recording.md @@ -0,0 +1,15 @@ +## Data recording + +### Buffer mode +**TODO** + +### Data flushing +Measurements can be performed over many hours or multiple days. +To make sure that unforeseen accidents like Windows Updates do not ruin the entire measurement, +the data can be continuously written to the disk. +To enable this feature, set `Flush After` to a non-zero value (in the Measurement Settings tab). +After \ number of datapoints are recorded, they are written to the cache directory +as serialized numpy array. +When the measurement is finished, all partial arrays are loaded again and the complete data +can be saved as `csv` or serialized numpy array (`.pkl`). + diff --git a/cpdctrl_gui/resources/led_control.md b/cpdctrl_gui/resources/led_control.md new file mode 100644 index 0000000..8b7b35e --- /dev/null +++ b/cpdctrl_gui/resources/led_control.md @@ -0,0 +1,42 @@ +## LED Control +To automatically control the LED state during the measurement, you can +use a LED script. +The LED script is a simple text file with lines in this format: +` ` +The script starts at the top, applies `` for `` and moves to the next line. + +- ``: Integer, time in seconds. Alternatively, you may append `s`, `m` or `h`, for seconds, minutes, hours. +- ``: LED brightness in percent. Can be an integer between 0-100 or `on` or `off`. + Numbers between 1-99 may only be used if the LED controller supports that (the Arduino/LEDD1B does not). + +Comments may start with `#`. Leading and trailing whitespaces as well as empty lines are ignored. + +### Example +``` +# turn on (100%) for 20 seconds +20 on +5 off# comments after statements allowed +5 100 # whitespace before comments after statements allowed +# turn off for 5 seconds +5s 0 +# set to 65% for 1 minute +1m 65 +# turn off for 1 minute and 10 seconds +1m10s off +# turn on off for 1 hour, 5 minutes and 45 seconds +1h5m45s on +``` + +### Live changes +The script can be adapted during the measurement in two ways: +First, in the GUI through the table in the `LED Script` tab on the left. +Second, by modifying the text file that was loaded (requires "Watch LED Script" checkbox to be ticked). + +You can: +- change any LED value +- change durations of future steps +- add/remove future lines (currently only by modifying the file) + +You can not: +- change steps from the past +- make the current step so short that it would be skipped \ No newline at end of file diff --git a/cpdctrl_gui/resources/sample_changing.md b/cpdctrl_gui/resources/sample_changing.md new file mode 100644 index 0000000..5e9bec2 --- /dev/null +++ b/cpdctrl_gui/resources/sample_changing.md @@ -0,0 +1,42 @@ +## Changing the sample +1. Stop the turbopump: + 1. Press the stop button on the control panel + 2. Close the valve between pump and chamber (on top of the turbo pump), by screwing it into the pump +2. Ventilate the chamber + 1. Open the gate valve + 2. Carefully open the needle valve (usually less than 1/8 turn) + 3. When the chamber can be moved, close both valves again +3. Take off the chamber + 1. Make some space to put the chamber (mind the grease on the bottom edge) + 2. Carefully lift the chamber. Good places to grab it are the tubing on the left and the right bottom edge. + ***Caution***: The tube connecting the chamber with the pump will exert a force to the right! + **Make sure you do not crash the chamber into the sample tray!** +4. Move the sample away from the gold mesh + 1. Move the tray fully down by turning the upper micrometer screw *counterclockwise* + 2. Move the tray fully to the left using the larger micrometer screw + 3. Loosen the screw on the clamp holding the sample in place and remove the sample +5. Prepare the new sample + 1. Put the sample on the tray and place the clamp on it. + 2. Tighten the clamp screw (you might need to grab the nut on the bottom side) + 3. Move the tray in x-position using the large micrometer screw. + The gold mesh should be well aligned with the sample surface + 4. Carefully move the tray up by turning the small micrometer screw *clockwise*. + Leave about half a millimeter of space between the sample and the gold mesh + **Do not crash the sample into the gold mesh!** +6. Put the chamber back on + 1. Put the chamber back on and align it on the two posts on the bottom and right + 2. Check the backside to make sure that no cables are sticking out +7. Vacuum the chamber + 1. Make sure the nitrogen venting valves are closed + 2. **Make sure the turbopump is not spinning anymore** + 3. When the turbopump has fully stopped, slowly open the valve on top of the pump + 4. Fully open the valve + 5. **Wait** until the vacuum is in the lower 1E-2 range, eg 4.0E-2 (read the value from the sensor attached to the turbopump) + 6. Only when this target pressure is reached, turn on the turbopump (control panel on top shelf) + 7. Wait until the desired vacuum for measurement is reached (usually 1E-5) + - 1E-4: **TODO** + - 1E-5: **TODO** + - 1E-6: after 1-2 days + + If the pressure does not drop enough: Use isopropanol to inspect for leaks + diff --git a/cpdctrl_gui/resources/small_about.md b/cpdctrl_gui/resources/small_about.md new file mode 100644 index 0000000..d2a717e --- /dev/null +++ b/cpdctrl_gui/resources/small_about.md @@ -0,0 +1,5 @@ +## About + +- Author: Matthias Quintern +- License: [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html) +- Source Code: https://git.quintern.xyz/MatthiasQuintern/cpdctrl-gui \ No newline at end of file diff --git a/cpdctrl_gui/resources/technical_information.md b/cpdctrl_gui/resources/technical_information.md new file mode 100644 index 0000000..e561289 --- /dev/null +++ b/cpdctrl_gui/resources/technical_information.md @@ -0,0 +1,23 @@ +## Technical Information +### cpdctrl-gui Software +`cpdctrl-gui` is written in python and uses the Qt framework for the graphical user interface. +Most functionality like interaction with the various devices is provided by the `cpdctrl` package, +which can be used without the GUI in an ipython shell for advanced or debugging purposes. + + + +### Arduino Code +To control the ThorLabs LEDD1B, it is connected to an Arduino Nano, which is connected to the lab PC +via USB. The Arduino must have the correct software loaded in order to communicate with `cpdctrl` and the LEDD1B. +1. Open the Arduino IDE +2. Open the sketch `~/cpd-dev/cpdctrl/arduino-thorlabs-led/led_control/led_control.ino` +3. Select `COM4` and `Arduino Nano` and then 'Upload Sketch' + +### CPD Controller Calibration +**TODO** +This process is not always necessary. +1. Set CPD Controller to Manual, Offset=Gold Work Function, Filter off (so you can see what happens immediately), Oscillator to ~2 +2. Move the micrometer screw close until a signal appears +3. Set Automatic offset so that the output is 0V + + diff --git a/cpdctrl_gui/resources/troubleshooting.md b/cpdctrl_gui/resources/troubleshooting.md index 8bcf000..a3d8001 100644 --- a/cpdctrl_gui/resources/troubleshooting.md +++ b/cpdctrl_gui/resources/troubleshooting.md @@ -1,6 +1,10 @@ ## Troubleshooting -### 1) The LED does not work +### 1) General +The program writes information to a log file, which might contain helpful information. +It is located in the cache directory, usually in `~/.cache/cpdctrl-gui`. + +### 2) The Arduino/LEDD1B does not work 1) Make sure the Thorlabs LED is plugged in, turned on (turn the dial to a non-zero value) and that the switch is in the **TRIG**er position 2) Reset the Arduino by pressing the white button on the top and restart the program 3) Unplug and replug the Arduino and restart the program -4) Re-upload the code (see section "Arduino Code") \ No newline at end of file +4) Re-upload the code (see section "Technical Information/Arduino Code") diff --git a/cpdctrl_gui/resources/user_guide.md b/cpdctrl_gui/resources/user_guide.md index 7160a4c..5ac9983 100644 --- a/cpdctrl_gui/resources/user_guide.md +++ b/cpdctrl_gui/resources/user_guide.md @@ -1,54 +1 @@ -# CPD User Guide -## Changing the sample -1. Stop the turbopump: - 1. Press the stop button on the control panel - 2. Close the valve between pump and chamber (on top of the turbo pump), by screwing it into the pump -2. Ventilate the chamber - 1. Open the gate valve - 2. Carefully open the needle valve (usually less than 1/8 turn) - 3. When the chamber can be moved, close both valves again -3. Take off the chamber - 1. Make some space to put the chamber (mind the grease on the bottom edge) - 2. Carefully lift the chamber. Good places to grab it are the tubing on the left and the right bottom edge. - ***Caution***: The tube connecting the chamber with the pump will exert a force to the right! - **Make sure you do not crash the chamber into the sample tray!** -4. Move the sample away from the gold mesh - 1. Move the tray fully down by turning the upper micrometer screw *counterclockwise* - 2. Move the tray fully to the left using the larger micrometer screw - 3. Loosen the screw on the clamp holding the sample in place and remove the sample -5. Prepare the new sample - 1. Put the sample on the tray and place the clamp on it. - 2. Tighten the clamp screw (you might need to grab the nut on the bottom side) - 3. Move the tray in x-position using the large micrometer screw. - The gold mesh should be well aligned with the sample surface - 4. Carefully move the tray up by turning the small micrometer screw *clockwise*. - Leave about half a millimeter of space between the sample and the gold mesh - **Do not crash the sample into the gold mesh!** -6. Put the chamber back on - 1. Put the chamber back on and align it on the two posts on the bottom and right - 2. Check the backside to make sure that no cables are sticking out -7. Vacuum the chamber - 1. Make sure the nitrogen venting valves are closed - 2. **Make sure the turbopump is not spinning anymore** - 3. When the turbopump has fully stopped, very slowly open the valve on top of the pump - 4. Fully open the valve **TODO: hier noch irgendwelche checks?** - 5. **Wait** until the vacuum is in the lower $10^{-2}$ range (read the value from the sensor attached to the turbopump) - 6. Only when this target pressure is reached, turn on the turbopump (control panel on top) - 7. Wait until the desired vacuum for measurement is reached (usually $10^{-5}$) - - $10^{-4}$: **TODO** - - $10^{-5}$: **TODO** - - $10^{-6}$: after 1-2 days - - If the pressure does not drop enough: Use isopropanol to inspect for leaks - -## CPD Controller Calibration -**TODO** - -This process is not always necessary. -1. Set CPD Controller to Manual, Offset=Gold Work Function, Filter off (so you can see what happens immediately), Oscillator to ~2 -2. Move the micrometer screw close until a signal appears -3. Set Automatic offset so that the output is 0V -Calibration: Fresh graphite probe - -## cpdctrl-gui Software -**TODO** +## User Guide diff --git a/cpdctrl_gui/ui/main_window.py b/cpdctrl_gui/ui/main_window.py index f2dd1d5..41977d8 100644 --- a/cpdctrl_gui/ui/main_window.py +++ b/cpdctrl_gui/ui/main_window.py @@ -5,9 +5,10 @@ from PyQt6.QtCore import Qt, QTimer, QFileSystemWatcher from PyQt6.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QLabel, QStatusBar, QFileDialog, \ QVBoxLayout from PyQt6.QtWidgets import QTabWidget -from PyQt6.QtGui import QIcon, QPixmap, QAction, QKeySequence, QDragEnterEvent +from PyQt6.QtGui import QIcon, QAction, QKeySequence, QDragEnterEvent from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QMessageBox +from .widgets.help import HelpMenu from ..resources import get_resource_path from .widgets.menubar import MenuBar from .widgets.toolbar import ToolBar @@ -15,8 +16,9 @@ from .widgets.metadata_input import MetadataInput from cpdctrl_gui.ui.widgets.settings import MeasurementSettings, AppSettings from .widgets.plot import Plot from .widgets.device_select import ListChoice -from .widgets.about import MarkdownView +from .widgets.about import About from .widgets.led_script import LedScriptViewer +from .widgets.help import HelpMenu # from .widgets.treeview import TreeView import time @@ -332,7 +334,6 @@ class MainWindow(QMainWindow): raise RuntimeError("No led control device selected") self.idle_stop() - raise KeyError("Exception for testing") name = self.w_measurement_settings.get_value("name") script = self.w_measurement_settings.get_value("led_script") flush_after = self.w_measurement_settings.get_value("flush_after") @@ -615,17 +616,7 @@ class MainWindow(QMainWindow): buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok) buttons.accepted.connect(dialog.accept) dialog.setLayout(QVBoxLayout()) - # show the logo via a pixmap in a label - img_path = get_resource_path("icons/logo.svg") - pixmap = QPixmap(img_path) - pixmap = pixmap.scaled(128, 128, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) - # qt cant find the file - label = QLabel() - label.setPixmap(pixmap) - label.setAlignment(Qt.AlignmentFlag.AlignCenter) # center the image - dialog.layout().addWidget(label) - # show about.md - dialog.layout().addWidget(MarkdownView("about.md")) + dialog.layout().addWidget(About()) dialog.layout().addWidget(buttons) dialog.exec() @@ -635,9 +626,7 @@ class MainWindow(QMainWindow): buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok) buttons.accepted.connect(dialog.accept) dialog.setLayout(QVBoxLayout()) - # show help.md - #dialog.layout().addWidget(MarkdownView("troubleshooting.md")) - dialog.layout().addWidget(MarkdownView("user_guide.md")) + dialog.layout().addWidget(HelpMenu()) dialog.layout().addWidget(buttons) # set larger window size dialog.resize(800, 600) diff --git a/cpdctrl_gui/ui/widgets/about.py b/cpdctrl_gui/ui/widgets/about.py index 62ea3e7..1ae8fb4 100644 --- a/cpdctrl_gui/ui/widgets/about.py +++ b/cpdctrl_gui/ui/widgets/about.py @@ -1,5 +1,6 @@ -from PyQt6.QtWidgets import QTextBrowser -from PyQt6.QtGui import QDesktopServices +from PyQt6.QtWidgets import QTextBrowser, QWidget, QLabel, QVBoxLayout +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QDesktopServices, QPixmap from ...resources import get_resource_path @@ -8,19 +9,45 @@ import logging log = logging.getLogger(__name__) class MarkdownView(QTextBrowser): - def __init__(self, path): - super().__init__() + def __init__(self, parent=None): + super().__init__(parent) self.setReadOnly(True) - self.filepath = get_resource_path(path) - try: - with open(self.filepath, "r") as file: - content = file.read() - self.setMarkdown(content) - except FileNotFoundError: - log.error(f"File not found: {self.filepath}") - self.setMarkdown(f"## File not found\n`{self.filepath}`") - # open links with the OS web browser self.anchorClicked.connect(QDesktopServices.openUrl) # dont follow links self.setOpenLinks(False) + +class About(QWidget): + """ + Small about text with logo + """ + def __init__(self, parent=None): + super().__init__(parent) + self.setLayout(QVBoxLayout()) + # show the logo via a pixmap in a label + img_path = get_resource_path("icons/logo.svg") + pixmap = QPixmap(img_path) + pixmap = pixmap.scaled(128, 128, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) + # qt cant find the file + w_label = QLabel() + w_label.setPixmap(pixmap) + w_label.setAlignment(Qt.AlignmentFlag.AlignCenter) # center the image + self.layout().addWidget(w_label) + # show about.md + w_md_view = MarkdownView() + filepath = get_resource_path("small_about.md") + try: + with open(filepath, "r") as file: + content = file.read() + try: + from importlib.metadata import version + cpdversion = version('cpdctrl_gui') + content += f"\n- Version: {cpdversion}" + except Exception as e: + log.info(f"Failed to get cpdctrl_gui version: {e}") + # content += f"\n- Version: Unknown" + w_md_view.setMarkdown(content) + except FileNotFoundError: + log.error(f"File not found: {filepath}") + w_md_view.setMarkdown(f"## File not found\n`{filepath}`") + self.layout().addWidget(w_md_view) diff --git a/cpdctrl_gui/ui/widgets/help.py b/cpdctrl_gui/ui/widgets/help.py new file mode 100644 index 0000000..fd683e8 --- /dev/null +++ b/cpdctrl_gui/ui/widgets/help.py @@ -0,0 +1,49 @@ +from PyQt6.QtWidgets import QWidget, QTextBrowser, QHBoxLayout, QListWidget, QListWidgetItem +from PyQt6.QtGui import QDesktopServices +from ...resources import get_resource_path +from .about import MarkdownView + +import logging +log = logging.getLogger(__name__) + +# Ordered list of help files to use +GUI_FILES = [ + ("about.md", "About"), + ("sample_changing.md", "Changing the Sample"), + ("data_recording.md", "Data recording"), + ("led_control.md", "LED control"), + ("troubleshooting.md", "Troubleshooting"), + ("technical_information.md", "Technical Information"), +] + +class HelpMenu(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.setLayout(QHBoxLayout()) + self.w_list = QListWidget() + self.w_list.setMaximumWidth(200) + self.layout().addWidget(self.w_list) + self.w_viewer = MarkdownView() + self.layout().addWidget(self.w_viewer) + files = [(get_resource_path(f), name) for (f,name) in GUI_FILES] + for i, (f, name) in enumerate(files): + w_item = QListWidgetItem(name) + w_item.filepath = f + self.w_list.addItem(w_item) + if i == 0: + self.set_view(w_item) + self.w_list.setCurrentRow(0) + self.w_list.itemClicked.connect(self.set_view) + + def set_view(self, list_item): + try: + filepath = list_item.filepath + try: + with open(filepath, "r") as file: + content = file.read() + self.w_viewer.setMarkdown(content) + except FileNotFoundError: + log.error(f"File not found: {filepath}") + self.w_viewer.setMarkdown(f"## File not found\n`{filepath}`") + except AttributeError: + log.error(f"Invalid list item") diff --git a/pyproject.toml b/pyproject.toml index 359e3b2..f1bd333 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,9 +2,9 @@ requires = ["setuptools"] [project] -name = "cpdctrl-gui" -version = "0.1.0" -description = "GUI Utility for CPD measurements with a Keitley 2700 SMU and an Arduino-controlled light source" +name = "cpdctrl_gui" +version = "1.0.0" +description = "GUI Utility for CPD measurements with a Keitley 2700 SMU and a controlled light source" requires-python = ">=3.10" readme = "README.md" license = {file = "LICENSE"}