digital-trigger 0.1.2__tar.gz → 0.1.4__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.
- digital_trigger-0.1.4/LICENSE +21 -0
- {digital_trigger-0.1.2 → digital_trigger-0.1.4}/PKG-INFO +61 -32
- {digital_trigger-0.1.2 → digital_trigger-0.1.4}/README.md +59 -32
- {digital_trigger-0.1.2 → digital_trigger-0.1.4}/pyproject.toml +1 -1
- {digital_trigger-0.1.2 → digital_trigger-0.1.4}/src/digital_trigger/trigger.py +50 -5
- {digital_trigger-0.1.2 → digital_trigger-0.1.4}/src/digital_trigger.egg-info/PKG-INFO +61 -32
- {digital_trigger-0.1.2 → digital_trigger-0.1.4}/src/digital_trigger.egg-info/SOURCES.txt +1 -0
- {digital_trigger-0.1.2 → digital_trigger-0.1.4}/setup.cfg +0 -0
- {digital_trigger-0.1.2 → digital_trigger-0.1.4}/src/digital_trigger/__init__.py +0 -0
- {digital_trigger-0.1.2 → digital_trigger-0.1.4}/src/digital_trigger.egg-info/dependency_links.txt +0 -0
- {digital_trigger-0.1.2 → digital_trigger-0.1.4}/src/digital_trigger.egg-info/requires.txt +0 -0
- {digital_trigger-0.1.2 → digital_trigger-0.1.4}/src/digital_trigger.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Max Lovell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: digital-trigger
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Simple class for simplified pySerial interface for use in TTL event marking using e.g. PsychoPy.
|
|
5
5
|
Author-email: Max Lovell <max_lovell@hotmail.co.uk>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -19,7 +19,9 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
19
19
|
Classifier: Topic :: Scientific/Engineering
|
|
20
20
|
Requires-Python: >=3.8
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
22
23
|
Requires-Dist: pyserial>=3.0
|
|
24
|
+
Dynamic: license-file
|
|
23
25
|
|
|
24
26
|
# digital-trigger
|
|
25
27
|
|
|
@@ -36,14 +38,6 @@ Requires Python 3.8+ and [`pyserial`](https://pypi.org/project/pyserial/) (insta
|
|
|
36
38
|
In PsychoPy you can install packages in the Builder GUI by going to:
|
|
37
39
|
Tools > Plugins and packages manager > Packages > Open PIP terminal, and run `pip install digital-trigger`
|
|
38
40
|
|
|
39
|
-
To find COM port number:
|
|
40
|
-
- run the command `python -m serial.tools.list_ports -v`
|
|
41
|
-
- On Windows, open Device Manager, expand "Ports (COM & LPT)", unplug and replug to see which is your device
|
|
42
|
-
- On Mac run `ls /dev/cu.*` in Terminal and look for something like /dev/cu.usbserial-XXXX or /dev/cu.usbmodemXXXX — use the cu.* name, not tty.*.
|
|
43
|
-
- On Linux, run ls `/dev/ttyUSB* /dev/ttyACM*` — USB-serial adapters are usually `ttyUSB0`
|
|
44
|
-
- Arduino-style boards `ttyACM0; dmesg | tail` right after plugging in shows the assigned name, and you may need to add yourself to the dialout group for permission.
|
|
45
|
-
|
|
46
|
-
Make sure your COM port is set up with a latency of 1ms (or lower) and Baudrate of 115200. This can be done under Device Manager > COM port > Advanced on Windows.
|
|
47
41
|
|
|
48
42
|
## Usage
|
|
49
43
|
|
|
@@ -60,34 +54,21 @@ from digital_trigger import Trigger
|
|
|
60
54
|
port = Trigger('COM4', names=['cond_1', 'cond_2', 'stim_1', 'stim_2'])
|
|
61
55
|
```
|
|
62
56
|
|
|
63
|
-
Note you can only do this once per experiment.
|
|
64
|
-
|
|
65
|
-
Probably best to have a single code block jsut for this in your first routine even.
|
|
66
|
-
|
|
67
|
-
#### Begin Routine
|
|
68
|
-
```
|
|
69
|
-
trigger_opened = False
|
|
70
|
-
trigger_closed = False
|
|
71
|
-
```
|
|
57
|
+
Note you can only do this once per experiment or you will get an 'access/permission denied' error.
|
|
58
|
+
Watch out if you insert the same routine twice as a copy.
|
|
72
59
|
|
|
73
60
|
#### Each Frame
|
|
74
61
|
```
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if image.status == STARTED and not trigger_opened:
|
|
78
|
-
win.callOnFlip(port.open, 'stim_1')
|
|
79
|
-
trigger_opened = True
|
|
62
|
+
if image.status == STARTED and port.is_closed(condition):
|
|
63
|
+
win.callOnFlip(port.open, condition)
|
|
80
64
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
win.callOnFlip(port.close, 'stim_1')
|
|
84
|
-
trigger_closed = True
|
|
65
|
+
if image.status == FINISHED and port.is_open(condition):
|
|
66
|
+
win.callOnFlip(port.close, condition)
|
|
85
67
|
```
|
|
86
68
|
|
|
87
|
-
|
|
69
|
+
OR, more simply:
|
|
88
70
|
```
|
|
89
|
-
|
|
90
|
-
port.close('stim_1')
|
|
71
|
+
port.sync_to_component(condition, image, win)
|
|
91
72
|
```
|
|
92
73
|
|
|
93
74
|
#### End Experiment
|
|
@@ -119,7 +100,44 @@ with Trigger('COM4', names=['stim_1']) as port:
|
|
|
119
100
|
port.open('stim_1')
|
|
120
101
|
```
|
|
121
102
|
|
|
122
|
-
|
|
103
|
+
## Issues
|
|
104
|
+
|
|
105
|
+
### Finding COM port number
|
|
106
|
+
To find COM port number:
|
|
107
|
+
- run the command `python -m serial.tools.list_ports -v`
|
|
108
|
+
- On Windows, open Device Manager, expand "Ports (COM & LPT)", unplug and replug to see which is your device
|
|
109
|
+
- On Mac run `ls /dev/cu.*` in Terminal and look for something like /dev/cu.usbserial-XXXX or /dev/cu.usbmodemXXXX — use the cu.* name, not tty.*.
|
|
110
|
+
- On Linux, run ls `/dev/ttyUSB* /dev/ttyACM*` — USB-serial adapters are usually `ttyUSB0`
|
|
111
|
+
- Arduino-style boards `ttyACM0; dmesg | tail` right after plugging in shows the assigned name, and you may need to add yourself to the dialout group for permission.
|
|
112
|
+
|
|
113
|
+
Make sure your COM port is set up with a latency of 1ms (or lower) and Baudrate of 115200. This can be done under Device Manager > COM port > Advanced on Windows.
|
|
114
|
+
|
|
115
|
+
### Module not found on Mac
|
|
116
|
+
`ModuleNotFoundError: No module named 'digital_trigger'` error on Mac: PsychoPy 2025 issue with install path typo.
|
|
117
|
+
Confirm by running this inside a code component in psychopy:
|
|
118
|
+
```
|
|
119
|
+
import sys
|
|
120
|
+
print(sys.executable)
|
|
121
|
+
for p in sys.path:
|
|
122
|
+
print(" ", p)
|
|
123
|
+
```
|
|
124
|
+
see if package is installed to python3.1 instead of python3.10.
|
|
125
|
+
- also `show digital-trigger` in PsychoPy's pip terminal (after running `install digital-trigger`) should also state that the package is installed.
|
|
126
|
+
- try `find ~/.psychopy3 /Applications/PsychoPy.app -name "digital_trigger*" 2>/dev/null` in terminal
|
|
127
|
+
|
|
128
|
+
Either install directly using `/Applications/PsychoPy.app/Contents/MacOS/python -m pip install \
|
|
129
|
+
--target ~/.psychopy3/packages/lib/python/site-packages \
|
|
130
|
+
digital-trigger`
|
|
131
|
+
or move the directory by running this in a terminal:
|
|
132
|
+
```
|
|
133
|
+
mv /Applications/PsychoPy.app/Contents/Resources/lib/python3.10/site-packages/digital_trigger \
|
|
134
|
+
~/.psychopy3/packages/lib/python/site-packages/
|
|
135
|
+
|
|
136
|
+
mv /Applications/PsychoPy.app/Contents/Resources/lib/python3.10/site-packages/digital_trigger-0.1.2.dist-info \
|
|
137
|
+
~/.psychopy3/packages/lib/python/site-packages/
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Resources:
|
|
123
141
|
- https://www.blackboxtoolkit.com/support_usb_ttl_module.html
|
|
124
142
|
- https://www.blackboxtoolkit.com/docs/pdf/USBTTLv1r19.pdf
|
|
125
143
|
- https://psychopy.org/developers/pluginDevGuide.html#plugindevguide
|
|
@@ -134,14 +152,18 @@ source .venv/bin/activate
|
|
|
134
152
|
pip install --upgrade pip build twine pytest
|
|
135
153
|
```
|
|
136
154
|
|
|
155
|
+
run test.py from root with `python test.py`
|
|
156
|
+
|
|
137
157
|
re-run build:
|
|
138
158
|
```
|
|
139
159
|
rm -rf dist # clear old builds so nothing stale is uploaded
|
|
140
160
|
python3 -m build
|
|
141
161
|
ls dist # confirm the new version number is shown
|
|
142
|
-
twine upload --repository testpypi dist/*
|
|
143
162
|
```
|
|
144
163
|
|
|
164
|
+
Push to PyPi test with `twine upload --repository testpypi dist/*`
|
|
165
|
+
Push to PyPi with `twine upload dist/*`
|
|
166
|
+
|
|
145
167
|
#### test
|
|
146
168
|
|
|
147
169
|
```
|
|
@@ -172,5 +194,12 @@ print('ok', t.bitmask)
|
|
|
172
194
|
#### release
|
|
173
195
|
```
|
|
174
196
|
cd /path/to/project # back to the project folder
|
|
197
|
+
rm -rf dist
|
|
198
|
+
python -m build
|
|
175
199
|
twine upload dist/* # no --repository = real PyPI
|
|
176
200
|
```
|
|
201
|
+
OR github:
|
|
202
|
+
Releases > Draft a new release.
|
|
203
|
+
Choose a tag: type v0.1.3, click "Create new tag: v0.1.3 on publish".
|
|
204
|
+
Title: v0.1.3. Description: brief notes on what changed.
|
|
205
|
+
Click Publish release.
|
|
@@ -13,14 +13,6 @@ Requires Python 3.8+ and [`pyserial`](https://pypi.org/project/pyserial/) (insta
|
|
|
13
13
|
In PsychoPy you can install packages in the Builder GUI by going to:
|
|
14
14
|
Tools > Plugins and packages manager > Packages > Open PIP terminal, and run `pip install digital-trigger`
|
|
15
15
|
|
|
16
|
-
To find COM port number:
|
|
17
|
-
- run the command `python -m serial.tools.list_ports -v`
|
|
18
|
-
- On Windows, open Device Manager, expand "Ports (COM & LPT)", unplug and replug to see which is your device
|
|
19
|
-
- On Mac run `ls /dev/cu.*` in Terminal and look for something like /dev/cu.usbserial-XXXX or /dev/cu.usbmodemXXXX — use the cu.* name, not tty.*.
|
|
20
|
-
- On Linux, run ls `/dev/ttyUSB* /dev/ttyACM*` — USB-serial adapters are usually `ttyUSB0`
|
|
21
|
-
- Arduino-style boards `ttyACM0; dmesg | tail` right after plugging in shows the assigned name, and you may need to add yourself to the dialout group for permission.
|
|
22
|
-
|
|
23
|
-
Make sure your COM port is set up with a latency of 1ms (or lower) and Baudrate of 115200. This can be done under Device Manager > COM port > Advanced on Windows.
|
|
24
16
|
|
|
25
17
|
## Usage
|
|
26
18
|
|
|
@@ -37,34 +29,21 @@ from digital_trigger import Trigger
|
|
|
37
29
|
port = Trigger('COM4', names=['cond_1', 'cond_2', 'stim_1', 'stim_2'])
|
|
38
30
|
```
|
|
39
31
|
|
|
40
|
-
Note you can only do this once per experiment.
|
|
41
|
-
|
|
42
|
-
Probably best to have a single code block jsut for this in your first routine even.
|
|
43
|
-
|
|
44
|
-
#### Begin Routine
|
|
45
|
-
```
|
|
46
|
-
trigger_opened = False
|
|
47
|
-
trigger_closed = False
|
|
48
|
-
```
|
|
32
|
+
Note you can only do this once per experiment or you will get an 'access/permission denied' error.
|
|
33
|
+
Watch out if you insert the same routine twice as a copy.
|
|
49
34
|
|
|
50
35
|
#### Each Frame
|
|
51
36
|
```
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if image.status == STARTED and not trigger_opened:
|
|
55
|
-
win.callOnFlip(port.open, 'stim_1')
|
|
56
|
-
trigger_opened = True
|
|
37
|
+
if image.status == STARTED and port.is_closed(condition):
|
|
38
|
+
win.callOnFlip(port.open, condition)
|
|
57
39
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
win.callOnFlip(port.close, 'stim_1')
|
|
61
|
-
trigger_closed = True
|
|
40
|
+
if image.status == FINISHED and port.is_open(condition):
|
|
41
|
+
win.callOnFlip(port.close, condition)
|
|
62
42
|
```
|
|
63
43
|
|
|
64
|
-
|
|
44
|
+
OR, more simply:
|
|
65
45
|
```
|
|
66
|
-
|
|
67
|
-
port.close('stim_1')
|
|
46
|
+
port.sync_to_component(condition, image, win)
|
|
68
47
|
```
|
|
69
48
|
|
|
70
49
|
#### End Experiment
|
|
@@ -96,7 +75,44 @@ with Trigger('COM4', names=['stim_1']) as port:
|
|
|
96
75
|
port.open('stim_1')
|
|
97
76
|
```
|
|
98
77
|
|
|
99
|
-
|
|
78
|
+
## Issues
|
|
79
|
+
|
|
80
|
+
### Finding COM port number
|
|
81
|
+
To find COM port number:
|
|
82
|
+
- run the command `python -m serial.tools.list_ports -v`
|
|
83
|
+
- On Windows, open Device Manager, expand "Ports (COM & LPT)", unplug and replug to see which is your device
|
|
84
|
+
- On Mac run `ls /dev/cu.*` in Terminal and look for something like /dev/cu.usbserial-XXXX or /dev/cu.usbmodemXXXX — use the cu.* name, not tty.*.
|
|
85
|
+
- On Linux, run ls `/dev/ttyUSB* /dev/ttyACM*` — USB-serial adapters are usually `ttyUSB0`
|
|
86
|
+
- Arduino-style boards `ttyACM0; dmesg | tail` right after plugging in shows the assigned name, and you may need to add yourself to the dialout group for permission.
|
|
87
|
+
|
|
88
|
+
Make sure your COM port is set up with a latency of 1ms (or lower) and Baudrate of 115200. This can be done under Device Manager > COM port > Advanced on Windows.
|
|
89
|
+
|
|
90
|
+
### Module not found on Mac
|
|
91
|
+
`ModuleNotFoundError: No module named 'digital_trigger'` error on Mac: PsychoPy 2025 issue with install path typo.
|
|
92
|
+
Confirm by running this inside a code component in psychopy:
|
|
93
|
+
```
|
|
94
|
+
import sys
|
|
95
|
+
print(sys.executable)
|
|
96
|
+
for p in sys.path:
|
|
97
|
+
print(" ", p)
|
|
98
|
+
```
|
|
99
|
+
see if package is installed to python3.1 instead of python3.10.
|
|
100
|
+
- also `show digital-trigger` in PsychoPy's pip terminal (after running `install digital-trigger`) should also state that the package is installed.
|
|
101
|
+
- try `find ~/.psychopy3 /Applications/PsychoPy.app -name "digital_trigger*" 2>/dev/null` in terminal
|
|
102
|
+
|
|
103
|
+
Either install directly using `/Applications/PsychoPy.app/Contents/MacOS/python -m pip install \
|
|
104
|
+
--target ~/.psychopy3/packages/lib/python/site-packages \
|
|
105
|
+
digital-trigger`
|
|
106
|
+
or move the directory by running this in a terminal:
|
|
107
|
+
```
|
|
108
|
+
mv /Applications/PsychoPy.app/Contents/Resources/lib/python3.10/site-packages/digital_trigger \
|
|
109
|
+
~/.psychopy3/packages/lib/python/site-packages/
|
|
110
|
+
|
|
111
|
+
mv /Applications/PsychoPy.app/Contents/Resources/lib/python3.10/site-packages/digital_trigger-0.1.2.dist-info \
|
|
112
|
+
~/.psychopy3/packages/lib/python/site-packages/
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Resources:
|
|
100
116
|
- https://www.blackboxtoolkit.com/support_usb_ttl_module.html
|
|
101
117
|
- https://www.blackboxtoolkit.com/docs/pdf/USBTTLv1r19.pdf
|
|
102
118
|
- https://psychopy.org/developers/pluginDevGuide.html#plugindevguide
|
|
@@ -111,14 +127,18 @@ source .venv/bin/activate
|
|
|
111
127
|
pip install --upgrade pip build twine pytest
|
|
112
128
|
```
|
|
113
129
|
|
|
130
|
+
run test.py from root with `python test.py`
|
|
131
|
+
|
|
114
132
|
re-run build:
|
|
115
133
|
```
|
|
116
134
|
rm -rf dist # clear old builds so nothing stale is uploaded
|
|
117
135
|
python3 -m build
|
|
118
136
|
ls dist # confirm the new version number is shown
|
|
119
|
-
twine upload --repository testpypi dist/*
|
|
120
137
|
```
|
|
121
138
|
|
|
139
|
+
Push to PyPi test with `twine upload --repository testpypi dist/*`
|
|
140
|
+
Push to PyPi with `twine upload dist/*`
|
|
141
|
+
|
|
122
142
|
#### test
|
|
123
143
|
|
|
124
144
|
```
|
|
@@ -149,5 +169,12 @@ print('ok', t.bitmask)
|
|
|
149
169
|
#### release
|
|
150
170
|
```
|
|
151
171
|
cd /path/to/project # back to the project folder
|
|
172
|
+
rm -rf dist
|
|
173
|
+
python -m build
|
|
152
174
|
twine upload dist/* # no --repository = real PyPI
|
|
153
|
-
```
|
|
175
|
+
```
|
|
176
|
+
OR github:
|
|
177
|
+
Releases > Draft a new release.
|
|
178
|
+
Choose a tag: type v0.1.3, click "Create new tag: v0.1.3 on publish".
|
|
179
|
+
Title: v0.1.3. Description: brief notes on what changed.
|
|
180
|
+
Click Publish release.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "digital-trigger"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
8
8
|
description = "Simple class for simplified pySerial interface for use in TTL event marking using e.g. PsychoPy."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import serial
|
|
2
|
+
import atexit
|
|
2
3
|
|
|
3
4
|
class Trigger:
|
|
4
5
|
def __init__(self, port='COM3', baudrate=115200, timeout=0, names=None, simulate=False):
|
|
@@ -9,6 +10,7 @@ class Trigger:
|
|
|
9
10
|
self.port = serial.Serial(port, baudrate, timeout=timeout)
|
|
10
11
|
self.reset()
|
|
11
12
|
self.write()
|
|
13
|
+
atexit.register(self.stop)
|
|
12
14
|
|
|
13
15
|
# -- line number handling ------------------------------------------
|
|
14
16
|
|
|
@@ -66,17 +68,19 @@ class Trigger:
|
|
|
66
68
|
if not self.simulate:
|
|
67
69
|
self.port.write(payload.encode())
|
|
68
70
|
else:
|
|
71
|
+
self.open_lines()
|
|
69
72
|
print('Hex code written: ' + payload)
|
|
70
|
-
# self.open_lines()
|
|
71
73
|
|
|
72
74
|
def stop(self):
|
|
75
|
+
if self.simulate or not hasattr(self, 'port') or not self.port.is_open:
|
|
76
|
+
return # already stopped, or never opened
|
|
73
77
|
print('Shutting down port')
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
print('Port is closed: ', not self.port.is_open)
|
|
78
|
+
self.reset()
|
|
79
|
+
self.port.close()
|
|
80
|
+
print('Port is closed: ', not self.port.is_open)
|
|
78
81
|
|
|
79
82
|
# -- OPTIONAL EXTRAS -------------------------------------------------
|
|
83
|
+
|
|
80
84
|
# -- display -------------------------------------------------
|
|
81
85
|
|
|
82
86
|
def is_open(self, lines):
|
|
@@ -119,8 +123,49 @@ class Trigger:
|
|
|
119
123
|
line_names.append('unnamed')
|
|
120
124
|
return line_names
|
|
121
125
|
|
|
126
|
+
def hex(self):
|
|
127
|
+
return f"{self.bitmask:02X}"
|
|
128
|
+
|
|
129
|
+
def binary(self):
|
|
130
|
+
return f"{self.bitmask:08b}"
|
|
131
|
+
|
|
132
|
+
# -- psychopy --------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
def sync_to_component(self, line, component, win):
|
|
135
|
+
# TODO: this only really works with a single line due to is_closed() etc, so consider a rewrite there
|
|
136
|
+
# see https://github.com/psychopy/psychopy/blob/dev/psychopy/constants.py
|
|
137
|
+
# consider lazy import of `from psychopy.constants import STARTED, FINISHED` instead of ints
|
|
138
|
+
if component.status == 1 and self.is_closed(line):
|
|
139
|
+
win.callOnFlip(self.open, line)
|
|
140
|
+
elif component.status == -1 and self.is_open(line):
|
|
141
|
+
win.callOnFlip(self.close, line)
|
|
142
|
+
|
|
143
|
+
def watch(self, line, component):
|
|
144
|
+
# Register a component whose lifecycle should drive a trigger line.
|
|
145
|
+
# line opens when the component starts and closes when it finishes
|
|
146
|
+
# call self.tick(win) each frame to run the logic.
|
|
147
|
+
# Call this in the Begin Routine tab for each pairing
|
|
148
|
+
if not hasattr(self, '_watched'):
|
|
149
|
+
self._watched = []
|
|
150
|
+
self._watched.append((line, component))
|
|
151
|
+
|
|
152
|
+
def unwatch_all(self):
|
|
153
|
+
# Clears all registered (line, component) pairs.
|
|
154
|
+
# Call this in Begin Routine - is recommended
|
|
155
|
+
self._watched = []
|
|
156
|
+
|
|
157
|
+
def tick(self, win):
|
|
158
|
+
# Call in Each Frame: checks all watched components and fire triggers on the next flip.
|
|
159
|
+
if not hasattr(self, '_watched'):
|
|
160
|
+
return
|
|
161
|
+
for line, component in self._watched:
|
|
162
|
+
self.sync_to_component(line, component, win)
|
|
163
|
+
|
|
122
164
|
# -- context manager support ----------------------------------------
|
|
123
165
|
|
|
166
|
+
def __repr__(self):
|
|
167
|
+
return f"Trigger(bitmask={self.binary()}, open_lines={self.open_lines()})"
|
|
168
|
+
|
|
124
169
|
def __enter__(self):
|
|
125
170
|
return self
|
|
126
171
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: digital-trigger
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Simple class for simplified pySerial interface for use in TTL event marking using e.g. PsychoPy.
|
|
5
5
|
Author-email: Max Lovell <max_lovell@hotmail.co.uk>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -19,7 +19,9 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
19
19
|
Classifier: Topic :: Scientific/Engineering
|
|
20
20
|
Requires-Python: >=3.8
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
22
23
|
Requires-Dist: pyserial>=3.0
|
|
24
|
+
Dynamic: license-file
|
|
23
25
|
|
|
24
26
|
# digital-trigger
|
|
25
27
|
|
|
@@ -36,14 +38,6 @@ Requires Python 3.8+ and [`pyserial`](https://pypi.org/project/pyserial/) (insta
|
|
|
36
38
|
In PsychoPy you can install packages in the Builder GUI by going to:
|
|
37
39
|
Tools > Plugins and packages manager > Packages > Open PIP terminal, and run `pip install digital-trigger`
|
|
38
40
|
|
|
39
|
-
To find COM port number:
|
|
40
|
-
- run the command `python -m serial.tools.list_ports -v`
|
|
41
|
-
- On Windows, open Device Manager, expand "Ports (COM & LPT)", unplug and replug to see which is your device
|
|
42
|
-
- On Mac run `ls /dev/cu.*` in Terminal and look for something like /dev/cu.usbserial-XXXX or /dev/cu.usbmodemXXXX — use the cu.* name, not tty.*.
|
|
43
|
-
- On Linux, run ls `/dev/ttyUSB* /dev/ttyACM*` — USB-serial adapters are usually `ttyUSB0`
|
|
44
|
-
- Arduino-style boards `ttyACM0; dmesg | tail` right after plugging in shows the assigned name, and you may need to add yourself to the dialout group for permission.
|
|
45
|
-
|
|
46
|
-
Make sure your COM port is set up with a latency of 1ms (or lower) and Baudrate of 115200. This can be done under Device Manager > COM port > Advanced on Windows.
|
|
47
41
|
|
|
48
42
|
## Usage
|
|
49
43
|
|
|
@@ -60,34 +54,21 @@ from digital_trigger import Trigger
|
|
|
60
54
|
port = Trigger('COM4', names=['cond_1', 'cond_2', 'stim_1', 'stim_2'])
|
|
61
55
|
```
|
|
62
56
|
|
|
63
|
-
Note you can only do this once per experiment.
|
|
64
|
-
|
|
65
|
-
Probably best to have a single code block jsut for this in your first routine even.
|
|
66
|
-
|
|
67
|
-
#### Begin Routine
|
|
68
|
-
```
|
|
69
|
-
trigger_opened = False
|
|
70
|
-
trigger_closed = False
|
|
71
|
-
```
|
|
57
|
+
Note you can only do this once per experiment or you will get an 'access/permission denied' error.
|
|
58
|
+
Watch out if you insert the same routine twice as a copy.
|
|
72
59
|
|
|
73
60
|
#### Each Frame
|
|
74
61
|
```
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if image.status == STARTED and not trigger_opened:
|
|
78
|
-
win.callOnFlip(port.open, 'stim_1')
|
|
79
|
-
trigger_opened = True
|
|
62
|
+
if image.status == STARTED and port.is_closed(condition):
|
|
63
|
+
win.callOnFlip(port.open, condition)
|
|
80
64
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
win.callOnFlip(port.close, 'stim_1')
|
|
84
|
-
trigger_closed = True
|
|
65
|
+
if image.status == FINISHED and port.is_open(condition):
|
|
66
|
+
win.callOnFlip(port.close, condition)
|
|
85
67
|
```
|
|
86
68
|
|
|
87
|
-
|
|
69
|
+
OR, more simply:
|
|
88
70
|
```
|
|
89
|
-
|
|
90
|
-
port.close('stim_1')
|
|
71
|
+
port.sync_to_component(condition, image, win)
|
|
91
72
|
```
|
|
92
73
|
|
|
93
74
|
#### End Experiment
|
|
@@ -119,7 +100,44 @@ with Trigger('COM4', names=['stim_1']) as port:
|
|
|
119
100
|
port.open('stim_1')
|
|
120
101
|
```
|
|
121
102
|
|
|
122
|
-
|
|
103
|
+
## Issues
|
|
104
|
+
|
|
105
|
+
### Finding COM port number
|
|
106
|
+
To find COM port number:
|
|
107
|
+
- run the command `python -m serial.tools.list_ports -v`
|
|
108
|
+
- On Windows, open Device Manager, expand "Ports (COM & LPT)", unplug and replug to see which is your device
|
|
109
|
+
- On Mac run `ls /dev/cu.*` in Terminal and look for something like /dev/cu.usbserial-XXXX or /dev/cu.usbmodemXXXX — use the cu.* name, not tty.*.
|
|
110
|
+
- On Linux, run ls `/dev/ttyUSB* /dev/ttyACM*` — USB-serial adapters are usually `ttyUSB0`
|
|
111
|
+
- Arduino-style boards `ttyACM0; dmesg | tail` right after plugging in shows the assigned name, and you may need to add yourself to the dialout group for permission.
|
|
112
|
+
|
|
113
|
+
Make sure your COM port is set up with a latency of 1ms (or lower) and Baudrate of 115200. This can be done under Device Manager > COM port > Advanced on Windows.
|
|
114
|
+
|
|
115
|
+
### Module not found on Mac
|
|
116
|
+
`ModuleNotFoundError: No module named 'digital_trigger'` error on Mac: PsychoPy 2025 issue with install path typo.
|
|
117
|
+
Confirm by running this inside a code component in psychopy:
|
|
118
|
+
```
|
|
119
|
+
import sys
|
|
120
|
+
print(sys.executable)
|
|
121
|
+
for p in sys.path:
|
|
122
|
+
print(" ", p)
|
|
123
|
+
```
|
|
124
|
+
see if package is installed to python3.1 instead of python3.10.
|
|
125
|
+
- also `show digital-trigger` in PsychoPy's pip terminal (after running `install digital-trigger`) should also state that the package is installed.
|
|
126
|
+
- try `find ~/.psychopy3 /Applications/PsychoPy.app -name "digital_trigger*" 2>/dev/null` in terminal
|
|
127
|
+
|
|
128
|
+
Either install directly using `/Applications/PsychoPy.app/Contents/MacOS/python -m pip install \
|
|
129
|
+
--target ~/.psychopy3/packages/lib/python/site-packages \
|
|
130
|
+
digital-trigger`
|
|
131
|
+
or move the directory by running this in a terminal:
|
|
132
|
+
```
|
|
133
|
+
mv /Applications/PsychoPy.app/Contents/Resources/lib/python3.10/site-packages/digital_trigger \
|
|
134
|
+
~/.psychopy3/packages/lib/python/site-packages/
|
|
135
|
+
|
|
136
|
+
mv /Applications/PsychoPy.app/Contents/Resources/lib/python3.10/site-packages/digital_trigger-0.1.2.dist-info \
|
|
137
|
+
~/.psychopy3/packages/lib/python/site-packages/
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Resources:
|
|
123
141
|
- https://www.blackboxtoolkit.com/support_usb_ttl_module.html
|
|
124
142
|
- https://www.blackboxtoolkit.com/docs/pdf/USBTTLv1r19.pdf
|
|
125
143
|
- https://psychopy.org/developers/pluginDevGuide.html#plugindevguide
|
|
@@ -134,14 +152,18 @@ source .venv/bin/activate
|
|
|
134
152
|
pip install --upgrade pip build twine pytest
|
|
135
153
|
```
|
|
136
154
|
|
|
155
|
+
run test.py from root with `python test.py`
|
|
156
|
+
|
|
137
157
|
re-run build:
|
|
138
158
|
```
|
|
139
159
|
rm -rf dist # clear old builds so nothing stale is uploaded
|
|
140
160
|
python3 -m build
|
|
141
161
|
ls dist # confirm the new version number is shown
|
|
142
|
-
twine upload --repository testpypi dist/*
|
|
143
162
|
```
|
|
144
163
|
|
|
164
|
+
Push to PyPi test with `twine upload --repository testpypi dist/*`
|
|
165
|
+
Push to PyPi with `twine upload dist/*`
|
|
166
|
+
|
|
145
167
|
#### test
|
|
146
168
|
|
|
147
169
|
```
|
|
@@ -172,5 +194,12 @@ print('ok', t.bitmask)
|
|
|
172
194
|
#### release
|
|
173
195
|
```
|
|
174
196
|
cd /path/to/project # back to the project folder
|
|
197
|
+
rm -rf dist
|
|
198
|
+
python -m build
|
|
175
199
|
twine upload dist/* # no --repository = real PyPI
|
|
176
200
|
```
|
|
201
|
+
OR github:
|
|
202
|
+
Releases > Draft a new release.
|
|
203
|
+
Choose a tag: type v0.1.3, click "Create new tag: v0.1.3 on publish".
|
|
204
|
+
Title: v0.1.3. Description: brief notes on what changed.
|
|
205
|
+
Click Publish release.
|
|
File without changes
|
|
File without changes
|
{digital_trigger-0.1.2 → digital_trigger-0.1.4}/src/digital_trigger.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|