Skip to content

Commit

Permalink
GUI for N1471H (CAEN) (#63)
Browse files Browse the repository at this point in the history
* 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
3 people authored Sep 30, 2024
1 parent 940515f commit 946c997
Show file tree
Hide file tree
Showing 4 changed files with 832 additions and 0 deletions.
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

0 comments on commit 946c997

Please sign in to comment.