Skip to content

Commit bd8bbaa

Browse files
authored
Merge pull request #27 from SiLab-Bonn/constellation_satellite
Python updates + Constellation satellite
2 parents a7f74e2 + d756773 commit bd8bbaa

14 files changed

Lines changed: 288 additions & 279 deletions

File tree

.github/workflows/tests.yml

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,23 @@ on: push
55
jobs:
66
tests:
77
name: Test
8-
runs-on: ubuntu-20.04
8+
runs-on: ubuntu-latest
99
strategy:
1010
fail-fast: false
11+
matrix:
12+
python-version: ["3.11", "3.12", "3.13"]
1113
steps:
1214
# Check out repo
13-
- uses: actions/checkout@v3
14-
- name: Setup python
15-
uses: actions/setup-python@v4
15+
- uses: actions/checkout@v6
16+
- uses: actions/setup-python@v6
1617
with:
17-
python-version: "3.9"
18-
19-
- name: Qt5 defaults
20-
run: |
21-
sudo apt install qt5-default
18+
python-version: ${{ matrix.python-version }}
2219

2320
- name: Install pymosa
2421
run: |
2522
pip install --upgrade pip
2623
pip install -e .
2724
plugin_online_monitor pymosa/online_monitor
28-
29-
- name: Headless display setup
30-
uses: pyvista/setup-headless-display-action@v1
3125
3226
- name: Test
33-
run: pytest -s -v
27+
run: pytest -s -v

VERSION

Lines changed: 0 additions & 1 deletion
This file was deleted.

pymosa/__init__.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1 @@
1-
# http://stackoverflow.com/questions/17583443/what-is-the-correct-way-to-share-package-version-with-setup-py-and-the-package
2-
from pkg_resources import get_distribution, DistributionNotFound
3-
4-
try:
5-
__version__ = get_distribution('pymosa').version
6-
except DistributionNotFound:
7-
__version__ = '(local)'
1+
__version__ = "1.0.0"

pymosa/constellation/READMDE.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
title: "Pymosa"
3+
description: "Satellite for controlling a EUDET-type beam telescope using a MMC3 readout board"
4+
category: "Readout Systems"
5+
language: "Python"
6+
parent_class: "TransmitterSatellite"
7+
---
8+
9+
## Description
10+
11+
This satellite controls the readout of a EUDET-type beam telescope using a [MMC3 readout board](https://hdl.handle.net/20.500.11811/7265).
12+
13+
## Building
14+
15+
Clone the repository:
16+
17+
```sh
18+
git clone https://github.com/SiLab-Bonn/pymosa
19+
```
20+
21+
Install via:
22+
```sh
23+
cd pymosa
24+
pip install ConstellationDAQ
25+
pip install -e .
26+
```
27+
28+
## Usage
29+
30+
Set the correct IP address in [pymosa/m26.yaml](https://github.com/SiLab-Bonn/pymosa) for more information.
31+
32+
Start the satellite with:
33+
34+
```sh
35+
python pymosa/constellation/__main__.py -g testbeam -n ANEMONE
36+
```
37+
38+
## Parameters
39+
40+
| Configuration | Description | Type | Default Value |
41+
|-----------|-------------|------| ------|
42+
| `no_data_timeout` | (Required) No data timeout after which the scan will be aborted, in seconds; if 0, the timeout is disabled. | String | None |
43+
| `send_data` | (Required) TCP address to which the telescope data is send; to allow incoming connections on all interfaces use 0.0.0.0 | String | None |
44+
| `max_triggers` | (Required) Maximum number of triggers; if 0, there is no limit on the number of triggers. | Integer | None |
45+
| `scan_timeout` | (Optional) Timeout after which the scan will be stopped, in seconds; if 0, the timeout is disabled. | Integer | 0 |
46+
| `run_number` | (Optional) Base run number, will be automatically increased; if none is given, generate filename | Integer | None |
47+
| `output_folder` | (Optional) Output folder for the telescope data; if none is given, the current working directory is used. | String | None |
48+
| `m26_configuration_file` | (Optional) Configuration file for Mimosa26 sensors, if note stated a default one is used. | String | 'm26_config/m26_threshold_8.yaml' |
49+
| `m26_jtag_configuration` | (Optional) Send Mimosa26 configuration via JTAG. | String | True |
50+
| `enabled_m26_channels` | (Optional) Enabled RX channels, eg. ["M26_RX1", "M26_RX2", "M26_RX6"]; default None (=all planes) | String | None |
51+
52+
53+
### Configuration Example
54+
55+
An example configuration for the TJ-Monopix2 satellite which could be dropped into a Constellation configuration as a starting point:
56+
57+
```toml
58+
[pymosa.ANEMONE]
59+
run_number = "None"
60+
output_folder = "None"
61+
m26_configuration_file = "None"
62+
m26_jtag_configuration = true
63+
no_data_timeout = 30
64+
scan_timeout = 0
65+
max_triggers = 0
66+
send_data = 'tcp://127.0.0.1:8500'
67+
enabled_m26_channels = "None"
68+
```
69+
70+
## Metrics
71+
72+
The following metrics are distributed by this satellite and can be subscribed to. Timed metrics provide an interval in units of time, triggered metrics in number of calls.
73+
74+
| Metric | Description | Value Type | Interval |
75+
|--------|-------------|------------|----------|
76+
| `TRIGGER_NUMBER` | Number of received triggers | Int | 1s |
77+
78+
## Data
79+
80+
Data is saved in HDF5 format in the `output_folder`.

pymosa/constellation/__main__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from constellation.core.logging import setup_cli_logging
2+
from constellation.core.satellite import SatelliteArgumentParser
3+
from pymosa_satellite import Pymosa
4+
5+
def main(args=None):
6+
parser = SatelliteArgumentParser()
7+
args = vars(parser.parse_args(args))
8+
setup_cli_logging(args.pop("level"))
9+
s = Pymosa(**args)
10+
s.run_satellite()
11+
12+
if __name__ == "__main__":
13+
main()
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[Pymosa]
2+
3+
[Pymosa.ANEMONE]
4+
no_data_timeout = 30
5+
send_data = 'tcp://127.0.0.1:8500'
6+
max_triggers = 1000000
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
from constellation.core.configuration import Configuration
2+
from constellation.core.satellite import Satellite
3+
import time
4+
from constellation.core.cmdp import MetricsType
5+
from constellation.core.fsm import SatelliteState
6+
from constellation.core.monitoring import schedule_metric
7+
import os
8+
import yaml
9+
import pymosa
10+
from pymosa.m26 import m26
11+
import logging
12+
from pymosa.m26_raw_data import save_configuration_dict
13+
from time import sleep, strftime, time
14+
from tqdm import tqdm
15+
from typing import Any
16+
17+
18+
class Pymosa(Satellite):
19+
def __init__(self, *args, **kwargs):
20+
super().__init__(*args, **kwargs)
21+
22+
def do_initializing(self, config: Configuration) -> str:
23+
# Open Mimosa26 std. configuration
24+
pymosa_path = os.path.dirname(pymosa.__file__)
25+
with open(os.path.join(pymosa_path, 'm26_configuration.yaml'), 'r') as f:
26+
self.yaml_config = yaml.safe_load(f)
27+
self._load_config(config)
28+
# Create telescope object and load hardware configuration
29+
self.telescope = m26(conf=None) # None: use default hardware configuration
30+
return "init done"
31+
32+
def do_launching(self) -> str:
33+
# Initialize telescope hardware and set up parameters
34+
self.telescope.init(init_conf=self.yaml_config)
35+
return "launching done"
36+
37+
def do_run(self, payload=None) -> str:
38+
self._pre_run()
39+
with self.telescope.access_file():
40+
save_configuration_dict(self.telescope.raw_data_file.h5_file, 'configuration', self.telescope.telescope_conf)
41+
with self.telescope.readout(enabled_m26_channels=self.telescope.enabled_m26_channels):
42+
got_data = False
43+
start = time()
44+
while not self.stop_scan and not self._state_thread_evt.is_set():
45+
sleep(1.0)
46+
if not got_data:
47+
if self.telescope.m26_readout.data_words_per_second()[0] > 0:
48+
got_data = True
49+
logging.info('Taking data...')
50+
if self.telescope.max_triggers:
51+
self.pbar = tqdm(total=self.telescope.max_triggers, ncols=80)
52+
else:
53+
self.pbar = tqdm(total=self.telescope.scan_timeout, ncols=80)
54+
else:
55+
triggers = self.telescope.dut['TLU']['TRIGGER_COUNTER']
56+
try:
57+
if self.telescope.max_triggers:
58+
self.pbar.update(triggers - self.pbar.n)
59+
else:
60+
self.pbar.update(time() - start - self.pbar.n)
61+
except ValueError:
62+
pass
63+
if self.telescope.max_triggers and triggers >= self.telescope.max_triggers:
64+
self.stop_scan = True
65+
self.pbar.close()
66+
logging.info('Trigger limit was reached: %i' % self.telescope.max_triggers)
67+
logging.info('Total amount of triggers collected: %d', self.telescope.dut['TLU']['TRIGGER_COUNTER'])
68+
self.logger.removeHandler(self.fh)
69+
logging.info('Data Output Filename: %s', self.telescope.run_filename + '.h5')
70+
self.telescope.analyze()
71+
return "running done"
72+
73+
def do_landing(self) -> str:
74+
# Close the resources
75+
self.telescope.close()
76+
return "landing done"
77+
78+
def _pre_run(self) -> None:
79+
self.stop_scan = False
80+
# signal.signal(signal.SIGINT, self.telescope._signal_handler)
81+
logging.info('Press Ctrl-C to stop run')
82+
83+
# check for filename that is not in use
84+
while True:
85+
if not self.telescope.output_filename and self.telescope.run_number:
86+
filename = 'run_' + str(self.telescope.run_number) + '_' + self.telescope.run_id
87+
88+
else:
89+
if self.telescope.output_filename:
90+
filename = self.telescope.output_filename
91+
else:
92+
filename = strftime("%Y%m%d-%H%M%S") + '_' + self.telescope.run_id
93+
if filename in [os.path.splitext(f)[0] for f in os.listdir(self.telescope.working_dir) if os.path.isfile(os.path.join(self.telescope.working_dir, f))]:
94+
if not self.telescope.output_filename and self.telescope.run_number:
95+
self.telescope.run_number += 1 # increase run number and try again
96+
continue
97+
else:
98+
raise IOError("Filename %s already exists." % filename)
99+
else:
100+
self.telescope.run_filename = os.path.join(self.telescope.working_dir, filename)
101+
break
102+
103+
# set up logger
104+
self.fh = logging.FileHandler(self.telescope.run_filename + '.log')
105+
self.fh.setLevel(logging.DEBUG)
106+
FORMAT = '%(asctime)s [%(name)-17s] - %(levelname)-7s %(message)s'
107+
self.fh.setFormatter(logging.Formatter(FORMAT))
108+
self.logger = logging.getLogger()
109+
self.logger.addHandler(self.fh)
110+
self.telescope.dut['TLU']['TRIGGER_COUNTER'] = 0
111+
112+
def _load_config(self, config: Configuration) -> None:
113+
config.set_default(key='scan_timeout', value=None)
114+
config.set_default(key='run_number', value=None)
115+
config.set_default(key='output_folder', value=None)
116+
config.set_default(key='m26_configuration_file', value=None)
117+
config.set_default(key='m26_jtag_configuration', value=True)
118+
config.set_default(key='enabled_m26_channels', value=None)
119+
120+
self.yaml_config['no_data_timeout'] = config.get(key='no_data_timeout')
121+
self.yaml_config['send_data'] = config.get(key='send_data')
122+
self.yaml_config['max_triggers'] = config.get(key='max_triggers')
123+
self.yaml_config['scan_timeout'] = config.get(key='scan_timeout')
124+
self.yaml_config['run_number'] = config.get(key='run_number')
125+
self.yaml_config['output_folder'] = config.get(key='output_folder')
126+
self.yaml_config['m26_configuration_file'] = config.get(key='m26_configuration_file')
127+
self.yaml_config['m26_jtag_configuration'] = config.get(key='m26_jtag_configuration')
128+
self.yaml_config['enabled_m26_channels'] = config.get(key='enabled_m26_channels')
129+
130+
@schedule_metric("", MetricsType.LAST_VALUE, 1)
131+
def trigger_number(self) -> int | None:
132+
if self.fsm.current_state_value == SatelliteState.RUN:
133+
return self.telescope.dut['TLU']['TRIGGER_COUNTER']
134+
else:
135+
return None

pymosa/m26_readout.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import datetime
33
from time import sleep, time, mktime
44
from threading import Thread, Event, Lock, Condition
5-
from collections import deque, Iterable
5+
from collections import deque
6+
from collections.abc import Iterable
67
import sys
78

89
import numpy as np

pymosa/online_monitor/start_pymosa_online_monitor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import subprocess
1010

1111
import psutil
12-
from PyQt5.QtWidgets import QApplication
12+
from PyQt6.QtWidgets import QApplication
1313

1414
from online_monitor.OnlineMonitor import OnlineMonitorApplication
1515
from online_monitor.utils import utils

0 commit comments

Comments
 (0)