Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GUI for N1471H (CAEN) #63

Merged
merged 40 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9a50599
structure of the GUIs
lobis Aug 26, 2024
4c7a7e6
Add new CAEN GUI
AlvaroEzq Aug 27, 2024
f5829bf
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 27, 2024
b3146bf
Add documentation
AlvaroEzq Aug 27, 2024
5b46df7
avoid adding class members dynamically
lobis Aug 27, 2024
c15e926
fix lambda
lobis Aug 27, 2024
9cade2c
unused event
lobis Aug 27, 2024
eb8faea
add cli arg parsing
lobis Aug 27, 2024
fe9d959
lambda
lobis Aug 27, 2024
7768a89
rename
lobis Aug 27, 2024
927d4b3
Add caenSimulator module
AlvaroEzq Aug 27, 2024
f1acae6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 27, 2024
ac5cc36
Address https://github.com/lobis/hvps/pull/63#issuecomment-2313152396
lobis Aug 28, 2024
f8df5e5
Fix previous commit: Address #63 (comment)
AlvaroEzq Aug 28, 2024
63007ad
Customizable channel names
AlvaroEzq Aug 28, 2024
de7d7a0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 28, 2024
01ec623
rename to follow python guidelines
lobis Aug 28, 2024
bb09e1a
typo
lobis Aug 28, 2024
3bcc415
minor refactor
lobis Aug 28, 2024
fb37d19
ruff
lobis Aug 28, 2024
f8aa52d
Fix for v0.1.1
AlvaroEzq Aug 28, 2024
bc75e22
Erase 'clear & turn off all' button (not useful)
AlvaroEzq Aug 28, 2024
3f7c31a
Improve alarm frame aesthetics
AlvaroEzq Aug 28, 2024
63ab139
Improve apply_changes output message
AlvaroEzq Aug 28, 2024
14d6236
do not use default mutable arguments
lobis Aug 28, 2024
a839777
anotate method and address warnings
lobis Aug 28, 2024
9c897bd
missing members on init
lobis Aug 28, 2024
23c27dc
Use isinstance
AlvaroEzq Aug 29, 2024
77a0a96
Adjust number of decimals of vmon and imon
AlvaroEzq Aug 29, 2024
37ba768
Use context manager to open and close the connection
AlvaroEzq Aug 29, 2024
952408f
Change vmon and imon entries to labels
AlvaroEzq Sep 6, 2024
03831ef
Change main_frame to LabelFrame
AlvaroEzq Sep 6, 2024
200fd3c
Reduce unnecessary calls to the caen
AlvaroEzq Sep 6, 2024
42b5620
Allow the ToolTip text to update dynamically
AlvaroEzq Sep 6, 2024
6e23bd7
refactor CaenHVPSGUI initialization to accept a parent frame
AlvaroEzq Sep 6, 2024
65fb268
add column with labels for vset
AlvaroEzq Sep 8, 2024
e3af1c9
add 'Turn off multichannel' button
AlvaroEzq Sep 8, 2024
2a855d1
Bind <Return> key to set vset in the vset entries
AlvaroEzq Sep 10, 2024
67c0ea3
make the status indicators circular (aesthetics)
AlvaroEzq Sep 18, 2024
227939d
Update README
AlvaroEzq Sep 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions gui/CAEN-N1471H/README.md
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.
163 changes: 163 additions & 0 deletions gui/CAEN-N1471H/caen_simulator.py
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()])
Loading
Loading