-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* structure of the GUIs * Add new CAEN GUI * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add documentation * avoid adding class members dynamically * fix lambda * unused event * add cli arg parsing * lambda * rename * Add caenSimulator module * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Address #63 (comment) * Fix previous commit: Address #63 (comment) * Customizable channel names * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * rename to follow python guidelines * typo * minor refactor * ruff * Fix for v0.1.1 * Erase 'clear & turn off all' button (not useful) * Improve alarm frame aesthetics * Improve apply_changes output message * do not use default mutable arguments * anotate method and address warnings * missing members on init * Use isinstance * Adjust number of decimals of vmon and imon * Use context manager to open and close the connection * Change vmon and imon entries to labels * Change main_frame to LabelFrame * Reduce unnecessary calls to the caen * Allow the ToolTip text to update dynamically * refactor CaenHVPSGUI initialization to accept a parent frame * add column with labels for vset * add 'Turn off multichannel' button * Bind <Return> key to set vset in the vset entries * make the status indicators circular (aesthetics) * Update README --------- Co-authored-by: Alvaro Ezquerro <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
- Loading branch information
1 parent
940515f
commit 946c997
Showing
4 changed files
with
832 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# CaenHVPSGUI Documentation | ||
|
||
## Overview | ||
The `CaenHVPSGUI` is a graphical user interface (GUI) designed to control and monitor CAEN High Voltage Power Supply (HVPS) modules. The interface is built using Python's `tkinter` library, with threading to handle background tasks and maintain responsive interaction. The GUI supports various functionalities such as setting voltages, turning channels on or off, monitoring channel states, and handling alarms. | ||
|
||
The communication with the CAEN HVPS is done by the [hvps](https://github.com/lobis/hvps.git) python library. | ||
|
||
## Features | ||
- **Real-time Monitoring**: Voltage (`vmon`), current (`imon`), and state indicators are updated in real-time. | ||
- **Multi-Channel Support**: The GUI can handle modules with multiple channels, providing individual control and monitoring for each channel. | ||
- **Alarm and Interlock Management**: Visual indicators and tooltips provide status information on the module alarms and interlock. | ||
- **Threaded Background Processing**: Ensures the GUI remains responsive during long-running operations. | ||
- **Tooltips**: Interactive tooltips provide additional information when hovering over various GUI elements. | ||
|
||
## How to Use | ||
### Requirements | ||
Make sure to have tkinter installed in your system (check [this](https://stackoverflow.com/a/74607246) if you don't) and the hvps python library (check [hvps installation guide](https://github.com/lobis/hvps?tab=readme-ov-file#installation-%EF%B8%8F)). | ||
|
||
Download the [gui.py](gui.py) script or clone the repository. | ||
### Usage | ||
At the directory where this [gui.py](gui.py) is found, run | ||
``` bash | ||
python3 gui.py --port /dev/ttyUSB0 | ||
``` | ||
Note that you may need to change the port. To show available ports run | ||
``` bash | ||
python -m hvps --ports | ||
``` | ||
#### Test mode | ||
If you want to test the GUI without having the hardware available, copy the [caen_simulator.py](caen_simulator.py) module to the same directory where the [gui.py](gui.py) script is found and just run | ||
``` bash | ||
python3 gui.py --test | ||
``` | ||
|
||
## Code Structure | ||
|
||
### Main Classes | ||
- **ToolTip**: A class for creating and managing tooltips for GUI widgets. | ||
- **CaenHVPSGUI**: The main class for creating and managing the GUI. | ||
|
||
### Key Methods | ||
- GUI initializer and frames constructors: | ||
- `create_gui()`: Initializes the GUI, setting up frames for alarms, channels, and other controls. | ||
- `create_main_frame()`, `create_alarm_frame()`, `create_channels_frame()`: Methods for creating specific sections of the GUI. | ||
- `open_channel_property_window()`: Opens a window with advanced settings for a specific channel. | ||
- Action and user interaction handlers: | ||
- `start_background_threads()`: Starts background threads for reading values and processing commands. | ||
- `issue_command()`: Manages the queueing and execution of commands to the module. This is key to ensure the CAEN module does not receive multiple commands at the same time. Please, use this for any method that interacts with the CAEN module. | ||
- `read_values()`: Continuously reads and updates the displayed values for each channel. | ||
|
||
### GUI Components | ||
- **Alarm Frame**: Displays alarm and interlock indicators with buttons to clear signals. | ||
- **Channels Frame**: Lists all available channels with options to set voltages, monitor values, and toggle states. | ||
- **Multichannel Frame**: Provides batch operations on multiple channels. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
import threading | ||
import time | ||
import random | ||
|
||
|
||
# class that simulates the channel of the caen by giving a random value to its attributes vmon, imon and | ||
class ChannelSimulator: | ||
def __init__(self, trip_probability=0.02): | ||
# private attributes (they do not exist in the real device) | ||
self._trip_probability = trip_probability | ||
self._vset = 100 | ||
|
||
# public attributes (they exist in the real device) | ||
self.vset = 100 | ||
self.iset = 0.6 # uA # functionality not implemented | ||
self.vmon = self.vset | ||
self.imon = self.vset / 10e3 # (uA) lets say there is a resistance of 10 MOhm | ||
self.imdec = 3 # channel imon number of decimal digits (2 HR, 3 LR) | ||
self.rup = 3 # V/s | ||
self.rdw = 10 # V/s | ||
self.pdwn = "RAMP" # 'KILL' or 'RAMP' | ||
self.stat = { | ||
"ON": True, | ||
"RUP": False, | ||
"RDW": False, | ||
"OVC": False, | ||
"OVV": False, | ||
"UNV": False, | ||
"MAXV": False, | ||
"TRIP": False, | ||
"OVP": False, | ||
"OVT": False, | ||
"DIS": False, | ||
"KILL": False, | ||
"ILK": False, | ||
"NOCAL": False, | ||
} | ||
|
||
def _randomize(self): | ||
if self.stat["KILL"] or self.stat["DIS"]: | ||
self.stat["ON"] = False | ||
self.vmon = 0 | ||
self.imon = 0 | ||
return | ||
|
||
if self.stat["TRIP"] or self.stat["ILK"]: | ||
self.stat["ON"] = False | ||
if self.pdwn == "KILL": | ||
self.stat["KILL"] = ( | ||
True # not sure if it behaves like this in KILL mode | ||
) | ||
self.vmon = 0 | ||
self.imon = 0 | ||
return | ||
|
||
self.vmon = random.gauss(self._vset, 1.0 / 3) # 0.3 V standard deviation | ||
self.imon = random.gauss( | ||
self.vmon / 10e3, self.vmon / 100e3 | ||
) # (uA) lets say there is a resistance of 10 MOhm and 10% standard deviation | ||
|
||
if not self.stat["ON"]: | ||
self._vset -= self.rdw | ||
self.imon = -self.imon * 10 | ||
# self.stat["RDW"] = True # not sure | ||
if self._vset <= 0: | ||
self._vset = 0 | ||
self.vmon = 0 | ||
self.imon = 0 | ||
return | ||
|
||
if not self.stat["TRIP"]: | ||
self.stat["TRIP"] = random.random() < self._trip_probability | ||
|
||
# simulate ramp up and ramp down when the channel is ON | ||
if self._vset < self.vset: | ||
self.stat["RUP"] = True | ||
self.stat["RDW"] = False | ||
self._vset += self.rup | ||
self.imon = self.imon * 10 | ||
if self._vset > self.vset: | ||
self._vset = self.vset | ||
elif self._vset > self.vset: | ||
self.stat["RDW"] = True | ||
self.stat["RUP"] = False | ||
self._vset -= self.rdw | ||
self.imon = -self.imon * 10 | ||
if self._vset < self.vset: | ||
self._vset = self.vset | ||
else: | ||
self.stat["RUP"] = False | ||
self.stat["RDW"] = False | ||
|
||
def turn_on(self): | ||
self.stat["ON"] = True | ||
self.stat["TRIP"] = False | ||
self.stat["KILL"] = False | ||
self.stat["ILK"] = False | ||
|
||
def turn_off(self): | ||
self.stat["ON"] = False | ||
self.stat["KILL"] = False | ||
self.stat["TRIP"] = False | ||
self.stat["ILK"] = False # not sure | ||
|
||
|
||
class ModuleSimulator: | ||
def __init__(self, n_channels, trip_probability=0.05): | ||
self.name = "N1471H SIMULATOR" | ||
self.number_of_channels = n_channels | ||
self.channels = [ | ||
ChannelSimulator( | ||
1 - (1 - trip_probability) ** (1.0 / self.number_of_channels) | ||
) | ||
for i in range(self.number_of_channels) | ||
] | ||
self.board_alarm_status = { | ||
"CH0": False, | ||
"CH1": False, | ||
"CH2": False, | ||
"CH3": False, | ||
"PWFAIL": False, | ||
"OVP": False, | ||
"HVCKFAIL": False, | ||
} | ||
self.interlock_status = False | ||
self.interlock_mode = "CLOSED" | ||
|
||
# Start the device reading thread | ||
self.randomize_thread = threading.Thread( | ||
target=self.__continuous_randomize, daemon=True | ||
).start() | ||
|
||
def clear_alarm_signal(self): | ||
self.board_alarm_status = {k: False for k in self.board_alarm_status.keys()} | ||
for ch in self.channels: | ||
ch.stat["TRIP"] = False | ||
ch.stat["ILK"] = False | ||
|
||
def _randomize(self): | ||
# print(self.board_alarm_status, self.interlock_status) | ||
# check for trips and set the alarm signal | ||
for i, ch in enumerate(self.channels): | ||
if ch.stat["TRIP"]: | ||
self.board_alarm_status["CH" + str(i)] = True | ||
# print(f"Channel {i} trip") | ||
|
||
# do the alarm-intlck connection and act the interlock | ||
self.__connection_alarm_intlck() | ||
if self.interlock_status: | ||
for ch in self.channels: | ||
ch.stat["ILK"] = True | ||
|
||
# randomize the channels | ||
for ch in self.channels: | ||
ch._randomize() | ||
|
||
def __continuous_randomize(self, wait_seconds=1): | ||
while True: | ||
self._randomize() | ||
time.sleep(wait_seconds) | ||
|
||
def __connection_alarm_intlck(self): | ||
self.interlock_status = any([v for k, v in self.board_alarm_status.items()]) |
Oops, something went wrong.