pyTriggerSync 0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytriggersync-0.3/MANIFEST.in +3 -0
- pytriggersync-0.3/PKG-INFO +233 -0
- pytriggersync-0.3/README.md +211 -0
- pytriggersync-0.3/pyTriggerSync/__init__.py +6 -0
- pytriggersync-0.3/pyTriggerSync/config.json +9 -0
- pytriggersync-0.3/pyTriggerSync/driver.py +535 -0
- pytriggersync-0.3/pyTriggerSync/graphics/pause.png +0 -0
- pytriggersync-0.3/pyTriggerSync/graphics/play.png +0 -0
- pytriggersync-0.3/pyTriggerSync/graphics/refresh.png +0 -0
- pytriggersync-0.3/pyTriggerSync/graphics/stop.png +0 -0
- pytriggersync-0.3/pyTriggerSync/main.py +1257 -0
- pytriggersync-0.3/pyTriggerSync.egg-info/PKG-INFO +233 -0
- pytriggersync-0.3/pyTriggerSync.egg-info/SOURCES.txt +21 -0
- pytriggersync-0.3/pyTriggerSync.egg-info/dependency_links.txt +1 -0
- pytriggersync-0.3/pyTriggerSync.egg-info/entry_points.txt +2 -0
- pytriggersync-0.3/pyTriggerSync.egg-info/not-zip-safe +1 -0
- pytriggersync-0.3/pyTriggerSync.egg-info/requires.txt +1 -0
- pytriggersync-0.3/pyTriggerSync.egg-info/top_level.txt +1 -0
- pytriggersync-0.3/requirements_strict.txt +1 -0
- pytriggersync-0.3/setup.cfg +4 -0
- pytriggersync-0.3/setup.py +26 -0
- pytriggersync-0.3/tests/test_driver.py +345 -0
- pytriggersync-0.3/tests/test_serial_protocol.py +378 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyTriggerSync
|
|
3
|
+
Version: 0.3
|
|
4
|
+
Summary: A python library/GUI.
|
|
5
|
+
Home-page: https://github.com/MicheleCotrufo/
|
|
6
|
+
Author: Michele Cotrufo
|
|
7
|
+
Author-email: michele.cotrufo@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: pyserial
|
|
11
|
+
Dynamic: author
|
|
12
|
+
Dynamic: author-email
|
|
13
|
+
Dynamic: description
|
|
14
|
+
Dynamic: description-content-type
|
|
15
|
+
Dynamic: home-page
|
|
16
|
+
Dynamic: license
|
|
17
|
+
Dynamic: requires-dist
|
|
18
|
+
Dynamic: summary
|
|
19
|
+
|
|
20
|
+
# pyTriggerSync
|
|
21
|
+
|
|
22
|
+
`pyTriggerSync` is a Python library/GUI interface for a Teensy microcontroller running the `Send_Trigger_Synced_With_External_Trigger_OnlyRemoteControl` firmware. The Teensy generates an output trigger pulse synchronized to an external trigger signal received on its input pin, either continuously or in user-controlled bursts. The package is composed of two parts: a low-level driver for serial communication, and a high-level GUI written with PyQt5 that can be easily embedded into other GUIs.
|
|
23
|
+
|
|
24
|
+
The interface can work either as a stand-alone application, or as a module of [ergastirio](https://github.com/MicheleCotrufo/ergastirio).
|
|
25
|
+
|
|
26
|
+
## Table of Contents
|
|
27
|
+
- [Installation](#installation)
|
|
28
|
+
- [Usage via the low-level driver](#usage-via-the-low-level-driver)
|
|
29
|
+
* [Creating a driver instance](#creating-a-driver-instance)
|
|
30
|
+
* [Properties](#properties)
|
|
31
|
+
* [Other attributes](#other-attributes)
|
|
32
|
+
* [Methods](#methods)
|
|
33
|
+
* [Examples](#examples)
|
|
34
|
+
- [Usage as a stand-alone GUI interface](#usage-as-a-stand-alone-gui-interface)
|
|
35
|
+
- [Embed the GUI within another GUI](#embed-the-gui-within-another-gui)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
Use the package manager pip to install,
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install pyTriggerSync
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This will install `pyTriggerSync` together with `pyserial`, which is required by the low-level driver. In order to use the GUI, it is also necessary to install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install abstract_instrument_interface>=0.10
|
|
50
|
+
pip install "PyQt5>=5.15.6"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage via the low-level driver
|
|
54
|
+
|
|
55
|
+
`pyTriggerSync` provides a low-level driver to communicate with the Teensy device over a USB serial connection.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from pyTriggerSync.driver import pyTriggerSync
|
|
59
|
+
device = pyTriggerSync()
|
|
60
|
+
available_devices = device.list_devices()
|
|
61
|
+
print(available_devices)
|
|
62
|
+
device.connect_device(port=available_devices[0].split(':')[0])
|
|
63
|
+
print(device.mode)
|
|
64
|
+
device.disconnect_device()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The method `list_devices()` scans all serial ports and returns a list of descriptor strings for compatible devices. Each string is in the form `"<port>: <description> [<hwid>]"`, e.g. `"COM4: USB Serial [USB VID:PID=16C0:0483 ...]"`. The port can be extracted from the first element by splitting on `':'`.
|
|
68
|
+
|
|
69
|
+
The class `pyTriggerSync` exposes several properties and methods to communicate with the device and read or change its settings. All properties and methods that communicate with the device require a connection to be active; they raise `RuntimeError` if called while disconnected.
|
|
70
|
+
|
|
71
|
+
### Creating a driver instance
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
pyTriggerSync(port='COM4', baudrate=115200, timeout=0.1)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
| Parameter | Type | Description |
|
|
78
|
+
| --- | --- | --- |
|
|
79
|
+
| `port` | str, optional | Default serial port to connect to. Used by `connect_device()` when no port is explicitly specified. Default is `'COM4'`. |
|
|
80
|
+
| `baudrate` | int, optional | Baud rate for the serial connection. Must match the firmware (115200). Default is `115200`. |
|
|
81
|
+
| `timeout` | float, optional | Read timeout in seconds for the serial connection. Default is `0.1`. |
|
|
82
|
+
|
|
83
|
+
### Properties
|
|
84
|
+
|
|
85
|
+
The following are implemented as Python `@property`, i.e. they are accessed without parentheses (e.g. `device.mode`) and, when settable, assigned with `=` (e.g. `device.mode = 1`). Reading or setting any of these requires a device to be connected, otherwise a `RuntimeError` is raised.
|
|
86
|
+
|
|
87
|
+
| Property | Type | Description | Can be set? |
|
|
88
|
+
| --- | --- | --- | --- |
|
|
89
|
+
| `identity` | str | The device's identity string (its response to `idn?`). | No |
|
|
90
|
+
| `mode` | int | Operating mode: `0` = Continuous Trigger (every qualifying input edge fires an output pulse), `1` = Trigger Controlled by User (output pulses only fire in response to `sendtrigger()`). | Yes â must be `0` or `1`; raises `ValueError` otherwise. |
|
|
91
|
+
| `polarity` | int | Output pulse polarity: `0` = Negative (output idles high, pulses low), `1` = Positive (output idles low, pulses high). | Yes â must be `0` or `1`; raises `ValueError` otherwise. |
|
|
92
|
+
| `delay` | int | Delay in nanoseconds between an input trigger edge and the generated output pulse. | Yes â must be a non-negative integer; raises `ValueError` otherwise. |
|
|
93
|
+
| `triggerduration` | int | Duration of the generated output pulse in nanoseconds. | Yes â must be a positive integer; raises `ValueError` otherwise. |
|
|
94
|
+
| `divider` | int | Sub-sampling divider: in Continuous Trigger mode, one output pulse is emitted for every `divider` input edges. `divider=1` passes every trigger through (no sub-sampling). | Yes â must be a positive integer; raises `ValueError` otherwise. |
|
|
95
|
+
| `number_of_triggers` | int | Number of output pulses produced by a single `sendtrigger()` call. Only meaningful in mode `1`. | Yes â must be a positive integer; raises `ValueError` otherwise. |
|
|
96
|
+
|
|
97
|
+
After setting a property, the driver reads the value back from the device to confirm it took effect. If the device does not acknowledge the command, or if the read-back value does not match the requested value, a `RuntimeError` is raised.
|
|
98
|
+
|
|
99
|
+
### Other attributes
|
|
100
|
+
|
|
101
|
+
These are plain instance attributes (not `@property`) that are useful to inspect directly.
|
|
102
|
+
|
|
103
|
+
| Attribute | Type | Description |
|
|
104
|
+
| --- | --- | --- |
|
|
105
|
+
| `connected` | bool | `True` if a device is currently connected, `False` otherwise. |
|
|
106
|
+
| `port` | str | Serial port used for the current (or most recent) connection. |
|
|
107
|
+
| `baudrate` | int | Baud rate configured for the connection. |
|
|
108
|
+
| `timeout` | float | Read timeout (in seconds) configured for the connection. |
|
|
109
|
+
| `identifier` | str | Substring that must appear at the start of a device's identity string for it to be recognised as compatible. Default is `'Send_Trigger_Synced_With_External_Trigger'`. |
|
|
110
|
+
|
|
111
|
+
### Methods
|
|
112
|
+
|
|
113
|
+
| Method | Returns | Description |
|
|
114
|
+
| --- | --- | --- |
|
|
115
|
+
| `list_devices()` | list of str | Scans all serial ports and returns a list of descriptor strings for compatible devices. Each string is in the form `"<port>: <description> [<hwid>]"`. Raises `RuntimeError` if a device is already connected. |
|
|
116
|
+
| `connect_device(port=None, baudrate=None, timeout=None)` | (str, int) | Attempts to connect to the device on the specified serial port. If any parameter is `None`, the corresponding instance attribute is used. On success, queries the device for all current settings and caches them. Returns `(message, 1)` on success or `(error, 0)` on failure. |
|
|
117
|
+
| `disconnect_device()` | (str, int) | Closes the serial connection to the currently connected device. Returns `(message, 1)` on success or `(error, 0)` on failure. |
|
|
118
|
+
| `check_valid_connection()` | None | Raises `RuntimeError` if no device is currently connected. Called internally by all properties and `sendtrigger()`. |
|
|
119
|
+
| `query(q)` | str | Sends the command string `q` to the device and returns its reply as a stripped string. Clears any stale data from the input buffer before writing. |
|
|
120
|
+
| `sendtrigger()` | bool | Sends a `trg` command to start a burst of output pulses. Only valid in mode `1`; raises `RuntimeError` in mode `0`. Returns `True` if the device acknowledged the command, `False` otherwise. Note: the acknowledgement confirms the burst was *armed*, not that it has completed â completion depends on input trigger edges arriving at the device and is reported asynchronously by the device itself (it sends `"01"` over serial when done). |
|
|
121
|
+
|
|
122
|
+
### Examples
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from pyTriggerSync.driver import pyTriggerSync
|
|
126
|
+
|
|
127
|
+
device = pyTriggerSync()
|
|
128
|
+
|
|
129
|
+
# Scan for available devices
|
|
130
|
+
available_devices = device.list_devices()
|
|
131
|
+
print(available_devices)
|
|
132
|
+
# e.g. ['COM4: USB Serial [USB VID:PID=16C0:0483 SER=123456 LOCATION=1-1]']
|
|
133
|
+
|
|
134
|
+
# Connect to the first available device
|
|
135
|
+
port = available_devices[0].split(':')[0]
|
|
136
|
+
device.connect_device(port=port)
|
|
137
|
+
|
|
138
|
+
# Read device identity
|
|
139
|
+
print(device.identity)
|
|
140
|
+
|
|
141
|
+
# Set mode to "Trigger Controlled by User"
|
|
142
|
+
device.mode = 1
|
|
143
|
+
|
|
144
|
+
# Set output polarity to Negative (idles high, pulses low)
|
|
145
|
+
device.polarity = 0
|
|
146
|
+
|
|
147
|
+
# Set a 500 ns delay and a 200 ns pulse duration
|
|
148
|
+
device.delay = 500
|
|
149
|
+
device.triggerduration = 200
|
|
150
|
+
|
|
151
|
+
# Configure a burst of 5 pulses and fire
|
|
152
|
+
device.number_of_triggers = 5
|
|
153
|
+
success = device.sendtrigger()
|
|
154
|
+
print(f"Trigger sent: {success}")
|
|
155
|
+
|
|
156
|
+
# Switch to Continuous Trigger mode with a divider of 2
|
|
157
|
+
# (fires one output pulse for every 2 input edges)
|
|
158
|
+
device.mode = 0
|
|
159
|
+
device.divider = 2
|
|
160
|
+
|
|
161
|
+
# Disconnect
|
|
162
|
+
device.disconnect_device()
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Usage as a stand-alone GUI interface
|
|
166
|
+
|
|
167
|
+
The installation sets up an entry point for the GUI. Just type
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
pyTriggerSync
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
in the command prompt to start the GUI.
|
|
174
|
+
|
|
175
|
+
## Embed the GUI within another GUI
|
|
176
|
+
|
|
177
|
+
The GUI can also be easily integrated within a larger graphical interface:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
import PyQt5.QtWidgets as Qt
|
|
181
|
+
import pyTriggerSync
|
|
182
|
+
|
|
183
|
+
app = Qt.QApplication([])
|
|
184
|
+
window = Qt.QWidget()
|
|
185
|
+
|
|
186
|
+
# The GUI must be contained inside a widget object
|
|
187
|
+
widget_containing_interface_GUI = Qt.QWidget()
|
|
188
|
+
widget_containing_interface_GUI.setStyleSheet(
|
|
189
|
+
".QWidget {
|
|
190
|
+
"
|
|
191
|
+
"border: 1px solid black;
|
|
192
|
+
"
|
|
193
|
+
"border-radius: 4px;
|
|
194
|
+
"
|
|
195
|
+
"}"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Create the interface (model) object
|
|
199
|
+
Interface = pyTriggerSync.interface(app=app)
|
|
200
|
+
Interface.verbose = False
|
|
201
|
+
|
|
202
|
+
# Signals emitted by the interface can be connected to external functions, e.g.:
|
|
203
|
+
#
|
|
204
|
+
# Interface.sig_mode_changed.connect(my_function)
|
|
205
|
+
#
|
|
206
|
+
# my_function will be called with the new mode (0 or 1) every time the mode changes.
|
|
207
|
+
# Similarly, Interface.sig_external_trigger_changed, sig_polarity_changed, etc.
|
|
208
|
+
# can be used to react to any setting change.
|
|
209
|
+
#
|
|
210
|
+
# To fire a trigger from external code (e.g. in response to another instrument):
|
|
211
|
+
#
|
|
212
|
+
# Interface.fire_trigger()
|
|
213
|
+
|
|
214
|
+
# Create the GUI (view + controller) and bind it to the interface
|
|
215
|
+
view = pyTriggerSync.gui(interface=Interface, parent=widget_containing_interface_GUI)
|
|
216
|
+
|
|
217
|
+
# Add additional GUI elements alongside the pyTriggerSync panel
|
|
218
|
+
gridlayoutwidget = Qt.QWidget()
|
|
219
|
+
gridlayout = Qt.QGridLayout()
|
|
220
|
+
gridlayout.addWidget(Qt.QLabel("Additional GUI 1"), 0, 0)
|
|
221
|
+
gridlayout.addWidget(Qt.QLabel("Additional GUI 2"), 1, 0)
|
|
222
|
+
gridlayoutwidget.setLayout(gridlayout)
|
|
223
|
+
|
|
224
|
+
layout = Qt.QVBoxLayout()
|
|
225
|
+
layout.addWidget(widget_containing_interface_GUI)
|
|
226
|
+
layout.addWidget(gridlayoutwidget)
|
|
227
|
+
layout.addStretch(1)
|
|
228
|
+
window.setLayout(layout)
|
|
229
|
+
|
|
230
|
+
app.aboutToQuit.connect(Interface.close)
|
|
231
|
+
window.show()
|
|
232
|
+
app.exec()
|
|
233
|
+
```
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# pyTriggerSync
|
|
2
|
+
|
|
3
|
+
`pyTriggerSync` is a Python library/GUI interface for a Teensy microcontroller running the `Send_Trigger_Synced_With_External_Trigger_OnlyRemoteControl` firmware. The Teensy generates an output trigger pulse synchronized to an external trigger signal received on its input pin, either continuously or in user-controlled bursts. The package is composed of two parts: a low-level driver for serial communication, and a high-level GUI written with PyQt5 that can be easily embedded into other GUIs.
|
|
4
|
+
|
|
5
|
+
The interface can work either as a stand-alone application, or as a module of [ergastirio](https://github.com/MicheleCotrufo/ergastirio).
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Usage via the low-level driver](#usage-via-the-low-level-driver)
|
|
10
|
+
* [Creating a driver instance](#creating-a-driver-instance)
|
|
11
|
+
* [Properties](#properties)
|
|
12
|
+
* [Other attributes](#other-attributes)
|
|
13
|
+
* [Methods](#methods)
|
|
14
|
+
* [Examples](#examples)
|
|
15
|
+
- [Usage as a stand-alone GUI interface](#usage-as-a-stand-alone-gui-interface)
|
|
16
|
+
- [Embed the GUI within another GUI](#embed-the-gui-within-another-gui)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Use the package manager pip to install,
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install pyTriggerSync
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This will install `pyTriggerSync` together with `pyserial`, which is required by the low-level driver. In order to use the GUI, it is also necessary to install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install abstract_instrument_interface>=0.10
|
|
31
|
+
pip install "PyQt5>=5.15.6"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage via the low-level driver
|
|
35
|
+
|
|
36
|
+
`pyTriggerSync` provides a low-level driver to communicate with the Teensy device over a USB serial connection.
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from pyTriggerSync.driver import pyTriggerSync
|
|
40
|
+
device = pyTriggerSync()
|
|
41
|
+
available_devices = device.list_devices()
|
|
42
|
+
print(available_devices)
|
|
43
|
+
device.connect_device(port=available_devices[0].split(':')[0])
|
|
44
|
+
print(device.mode)
|
|
45
|
+
device.disconnect_device()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The method `list_devices()` scans all serial ports and returns a list of descriptor strings for compatible devices. Each string is in the form `"<port>: <description> [<hwid>]"`, e.g. `"COM4: USB Serial [USB VID:PID=16C0:0483 ...]"`. The port can be extracted from the first element by splitting on `':'`.
|
|
49
|
+
|
|
50
|
+
The class `pyTriggerSync` exposes several properties and methods to communicate with the device and read or change its settings. All properties and methods that communicate with the device require a connection to be active; they raise `RuntimeError` if called while disconnected.
|
|
51
|
+
|
|
52
|
+
### Creating a driver instance
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
pyTriggerSync(port='COM4', baudrate=115200, timeout=0.1)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
| Parameter | Type | Description |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| `port` | str, optional | Default serial port to connect to. Used by `connect_device()` when no port is explicitly specified. Default is `'COM4'`. |
|
|
61
|
+
| `baudrate` | int, optional | Baud rate for the serial connection. Must match the firmware (115200). Default is `115200`. |
|
|
62
|
+
| `timeout` | float, optional | Read timeout in seconds for the serial connection. Default is `0.1`. |
|
|
63
|
+
|
|
64
|
+
### Properties
|
|
65
|
+
|
|
66
|
+
The following are implemented as Python `@property`, i.e. they are accessed without parentheses (e.g. `device.mode`) and, when settable, assigned with `=` (e.g. `device.mode = 1`). Reading or setting any of these requires a device to be connected, otherwise a `RuntimeError` is raised.
|
|
67
|
+
|
|
68
|
+
| Property | Type | Description | Can be set? |
|
|
69
|
+
| --- | --- | --- | --- |
|
|
70
|
+
| `identity` | str | The device's identity string (its response to `idn?`). | No |
|
|
71
|
+
| `mode` | int | Operating mode: `0` = Continuous Trigger (every qualifying input edge fires an output pulse), `1` = Trigger Controlled by User (output pulses only fire in response to `sendtrigger()`). | Yes — must be `0` or `1`; raises `ValueError` otherwise. |
|
|
72
|
+
| `polarity` | int | Output pulse polarity: `0` = Negative (output idles high, pulses low), `1` = Positive (output idles low, pulses high). | Yes — must be `0` or `1`; raises `ValueError` otherwise. |
|
|
73
|
+
| `delay` | int | Delay in nanoseconds between an input trigger edge and the generated output pulse. | Yes — must be a non-negative integer; raises `ValueError` otherwise. |
|
|
74
|
+
| `triggerduration` | int | Duration of the generated output pulse in nanoseconds. | Yes — must be a positive integer; raises `ValueError` otherwise. |
|
|
75
|
+
| `divider` | int | Sub-sampling divider: in Continuous Trigger mode, one output pulse is emitted for every `divider` input edges. `divider=1` passes every trigger through (no sub-sampling). | Yes — must be a positive integer; raises `ValueError` otherwise. |
|
|
76
|
+
| `number_of_triggers` | int | Number of output pulses produced by a single `sendtrigger()` call. Only meaningful in mode `1`. | Yes — must be a positive integer; raises `ValueError` otherwise. |
|
|
77
|
+
|
|
78
|
+
After setting a property, the driver reads the value back from the device to confirm it took effect. If the device does not acknowledge the command, or if the read-back value does not match the requested value, a `RuntimeError` is raised.
|
|
79
|
+
|
|
80
|
+
### Other attributes
|
|
81
|
+
|
|
82
|
+
These are plain instance attributes (not `@property`) that are useful to inspect directly.
|
|
83
|
+
|
|
84
|
+
| Attribute | Type | Description |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| `connected` | bool | `True` if a device is currently connected, `False` otherwise. |
|
|
87
|
+
| `port` | str | Serial port used for the current (or most recent) connection. |
|
|
88
|
+
| `baudrate` | int | Baud rate configured for the connection. |
|
|
89
|
+
| `timeout` | float | Read timeout (in seconds) configured for the connection. |
|
|
90
|
+
| `identifier` | str | Substring that must appear at the start of a device's identity string for it to be recognised as compatible. Default is `'Send_Trigger_Synced_With_External_Trigger'`. |
|
|
91
|
+
|
|
92
|
+
### Methods
|
|
93
|
+
|
|
94
|
+
| Method | Returns | Description |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| `list_devices()` | list of str | Scans all serial ports and returns a list of descriptor strings for compatible devices. Each string is in the form `"<port>: <description> [<hwid>]"`. Raises `RuntimeError` if a device is already connected. |
|
|
97
|
+
| `connect_device(port=None, baudrate=None, timeout=None)` | (str, int) | Attempts to connect to the device on the specified serial port. If any parameter is `None`, the corresponding instance attribute is used. On success, queries the device for all current settings and caches them. Returns `(message, 1)` on success or `(error, 0)` on failure. |
|
|
98
|
+
| `disconnect_device()` | (str, int) | Closes the serial connection to the currently connected device. Returns `(message, 1)` on success or `(error, 0)` on failure. |
|
|
99
|
+
| `check_valid_connection()` | None | Raises `RuntimeError` if no device is currently connected. Called internally by all properties and `sendtrigger()`. |
|
|
100
|
+
| `query(q)` | str | Sends the command string `q` to the device and returns its reply as a stripped string. Clears any stale data from the input buffer before writing. |
|
|
101
|
+
| `sendtrigger()` | bool | Sends a `trg` command to start a burst of output pulses. Only valid in mode `1`; raises `RuntimeError` in mode `0`. Returns `True` if the device acknowledged the command, `False` otherwise. Note: the acknowledgement confirms the burst was *armed*, not that it has completed — completion depends on input trigger edges arriving at the device and is reported asynchronously by the device itself (it sends `"01"` over serial when done). |
|
|
102
|
+
|
|
103
|
+
### Examples
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from pyTriggerSync.driver import pyTriggerSync
|
|
107
|
+
|
|
108
|
+
device = pyTriggerSync()
|
|
109
|
+
|
|
110
|
+
# Scan for available devices
|
|
111
|
+
available_devices = device.list_devices()
|
|
112
|
+
print(available_devices)
|
|
113
|
+
# e.g. ['COM4: USB Serial [USB VID:PID=16C0:0483 SER=123456 LOCATION=1-1]']
|
|
114
|
+
|
|
115
|
+
# Connect to the first available device
|
|
116
|
+
port = available_devices[0].split(':')[0]
|
|
117
|
+
device.connect_device(port=port)
|
|
118
|
+
|
|
119
|
+
# Read device identity
|
|
120
|
+
print(device.identity)
|
|
121
|
+
|
|
122
|
+
# Set mode to "Trigger Controlled by User"
|
|
123
|
+
device.mode = 1
|
|
124
|
+
|
|
125
|
+
# Set output polarity to Negative (idles high, pulses low)
|
|
126
|
+
device.polarity = 0
|
|
127
|
+
|
|
128
|
+
# Set a 500 ns delay and a 200 ns pulse duration
|
|
129
|
+
device.delay = 500
|
|
130
|
+
device.triggerduration = 200
|
|
131
|
+
|
|
132
|
+
# Configure a burst of 5 pulses and fire
|
|
133
|
+
device.number_of_triggers = 5
|
|
134
|
+
success = device.sendtrigger()
|
|
135
|
+
print(f"Trigger sent: {success}")
|
|
136
|
+
|
|
137
|
+
# Switch to Continuous Trigger mode with a divider of 2
|
|
138
|
+
# (fires one output pulse for every 2 input edges)
|
|
139
|
+
device.mode = 0
|
|
140
|
+
device.divider = 2
|
|
141
|
+
|
|
142
|
+
# Disconnect
|
|
143
|
+
device.disconnect_device()
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Usage as a stand-alone GUI interface
|
|
147
|
+
|
|
148
|
+
The installation sets up an entry point for the GUI. Just type
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
pyTriggerSync
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
in the command prompt to start the GUI.
|
|
155
|
+
|
|
156
|
+
## Embed the GUI within another GUI
|
|
157
|
+
|
|
158
|
+
The GUI can also be easily integrated within a larger graphical interface:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
import PyQt5.QtWidgets as Qt
|
|
162
|
+
import pyTriggerSync
|
|
163
|
+
|
|
164
|
+
app = Qt.QApplication([])
|
|
165
|
+
window = Qt.QWidget()
|
|
166
|
+
|
|
167
|
+
# The GUI must be contained inside a widget object
|
|
168
|
+
widget_containing_interface_GUI = Qt.QWidget()
|
|
169
|
+
widget_containing_interface_GUI.setStyleSheet(
|
|
170
|
+
".QWidget {\n"
|
|
171
|
+
"border: 1px solid black;\n"
|
|
172
|
+
"border-radius: 4px;\n"
|
|
173
|
+
"}"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Create the interface (model) object
|
|
177
|
+
Interface = pyTriggerSync.interface(app=app)
|
|
178
|
+
Interface.verbose = False
|
|
179
|
+
|
|
180
|
+
# Signals emitted by the interface can be connected to external functions, e.g.:
|
|
181
|
+
#
|
|
182
|
+
# Interface.sig_mode_changed.connect(my_function)
|
|
183
|
+
#
|
|
184
|
+
# my_function will be called with the new mode (0 or 1) every time the mode changes.
|
|
185
|
+
# Similarly, Interface.sig_external_trigger_changed, sig_polarity_changed, etc.
|
|
186
|
+
# can be used to react to any setting change.
|
|
187
|
+
#
|
|
188
|
+
# To fire a trigger from external code (e.g. in response to another instrument):
|
|
189
|
+
#
|
|
190
|
+
# Interface.fire_trigger()
|
|
191
|
+
|
|
192
|
+
# Create the GUI (view + controller) and bind it to the interface
|
|
193
|
+
view = pyTriggerSync.gui(interface=Interface, parent=widget_containing_interface_GUI)
|
|
194
|
+
|
|
195
|
+
# Add additional GUI elements alongside the pyTriggerSync panel
|
|
196
|
+
gridlayoutwidget = Qt.QWidget()
|
|
197
|
+
gridlayout = Qt.QGridLayout()
|
|
198
|
+
gridlayout.addWidget(Qt.QLabel("Additional GUI 1"), 0, 0)
|
|
199
|
+
gridlayout.addWidget(Qt.QLabel("Additional GUI 2"), 1, 0)
|
|
200
|
+
gridlayoutwidget.setLayout(gridlayout)
|
|
201
|
+
|
|
202
|
+
layout = Qt.QVBoxLayout()
|
|
203
|
+
layout.addWidget(widget_containing_interface_GUI)
|
|
204
|
+
layout.addWidget(gridlayoutwidget)
|
|
205
|
+
layout.addStretch(1)
|
|
206
|
+
window.setLayout(layout)
|
|
207
|
+
|
|
208
|
+
app.aboutToQuit.connect(Interface.close)
|
|
209
|
+
window.show()
|
|
210
|
+
app.exec()
|
|
211
|
+
```
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#If this package was installed only to use the low-level driver, the PyQt library is not necessarily installed. In this case, importing stuff from main.py would generate an error
|
|
2
|
+
import importlib.util
|
|
3
|
+
package_name = 'PyQt5'
|
|
4
|
+
spec = importlib.util.find_spec(package_name)
|
|
5
|
+
if spec:
|
|
6
|
+
from .main import interface, gui
|