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.
@@ -0,0 +1,3 @@
1
+ include pyTriggerSync/graphics/*
2
+ include pyTriggerSync/*.json
3
+ include requirements_strict.txt
@@ -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
@@ -0,0 +1,9 @@
1
+ {
2
+ "delay": 1200,
3
+ "divider": 3,
4
+ "external_trigger": true,
5
+ "mode": 1,
6
+ "number_of_triggers": 5,
7
+ "polarity": 1,
8
+ "triggerduration": 1000
9
+ }