ttl-barcoder 0.4.1__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.
- ttl_barcoder-0.4.1/.gitignore +31 -0
- ttl_barcoder-0.4.1/CITATION.cff +11 -0
- ttl_barcoder-0.4.1/LICENSE +29 -0
- ttl_barcoder-0.4.1/PKG-INFO +167 -0
- ttl_barcoder-0.4.1/README.md +127 -0
- ttl_barcoder-0.4.1/VERSION +1 -0
- ttl_barcoder-0.4.1/examples/bpod_loopback.py +161 -0
- ttl_barcoder-0.4.1/examples/dry_simulation.py +88 -0
- ttl_barcoder-0.4.1/examples/pigpio_send.py +112 -0
- ttl_barcoder-0.4.1/pyproject.toml +124 -0
- ttl_barcoder-0.4.1/src/ttl_barcoder/__init__.py +34 -0
- ttl_barcoder-0.4.1/src/ttl_barcoder/core/__init__.py +29 -0
- ttl_barcoder-0.4.1/src/ttl_barcoder/core/barcode_ttl.py +104 -0
- ttl_barcoder-0.4.1/src/ttl_barcoder/core/config.py +138 -0
- ttl_barcoder-0.4.1/src/ttl_barcoder/core/decoder.py +76 -0
- ttl_barcoder-0.4.1/src/ttl_barcoder/core/encoder.py +59 -0
- ttl_barcoder-0.4.1/src/ttl_barcoder/core/generator.py +124 -0
- ttl_barcoder-0.4.1/src/ttl_barcoder/hardware/__init__.py +0 -0
- ttl_barcoder-0.4.1/src/ttl_barcoder/hardware/bpod/__init__.py +3 -0
- ttl_barcoder-0.4.1/src/ttl_barcoder/hardware/bpod/sender.py +50 -0
- ttl_barcoder-0.4.1/src/ttl_barcoder/hardware/pigpio/__init__.py +3 -0
- ttl_barcoder-0.4.1/src/ttl_barcoder/hardware/pigpio/sender.py +112 -0
- ttl_barcoder-0.4.1/src/ttl_barcoder/py.typed +0 -0
- ttl_barcoder-0.4.1/tests/test_barcode_ttl.py +129 -0
- ttl_barcoder-0.4.1/tests/test_bpod.py +99 -0
- ttl_barcoder-0.4.1/tests/test_config.py +132 -0
- ttl_barcoder-0.4.1/tests/test_decoder.py +90 -0
- ttl_barcoder-0.4.1/tests/test_encoder.py +82 -0
- ttl_barcoder-0.4.1/tests/test_generator.py +132 -0
- ttl_barcoder-0.4.1/tests/test_smoke.py +98 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.so
|
|
5
|
+
*.egg
|
|
6
|
+
*.egg-info/
|
|
7
|
+
.eggs/
|
|
8
|
+
build/
|
|
9
|
+
dist/
|
|
10
|
+
|
|
11
|
+
# Testing
|
|
12
|
+
.coverage
|
|
13
|
+
.coverage.*
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
htmlcov/
|
|
16
|
+
.tox/
|
|
17
|
+
.nox/
|
|
18
|
+
|
|
19
|
+
# Type checkers / linters
|
|
20
|
+
.mypy_cache/
|
|
21
|
+
.ruff_cache/
|
|
22
|
+
|
|
23
|
+
# Virtual environments
|
|
24
|
+
.venv/
|
|
25
|
+
venv/
|
|
26
|
+
env/
|
|
27
|
+
|
|
28
|
+
# IDE / OS
|
|
29
|
+
.idea/
|
|
30
|
+
.vscode/
|
|
31
|
+
.DS_Store
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this software, please cite it as below."
|
|
3
|
+
type: software
|
|
4
|
+
title: "ttl-barcoder: TTL barcode generation for multi-system DAQ synchronization"
|
|
5
|
+
version: "0.4.0"
|
|
6
|
+
repository-code: https://github.com/murineshiftwork/ttl-barcoder
|
|
7
|
+
license: BSD-3-Clause
|
|
8
|
+
authors:
|
|
9
|
+
- family-names: Rollik
|
|
10
|
+
given-names: Lars B.
|
|
11
|
+
orcid: https://orcid.org/0000-0003-0160-6971
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, Lars B. Rollik
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
17
|
+
may be used to endorse or promote products derived from this software
|
|
18
|
+
without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ttl-barcoder
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: Modular barcode generation for TTL synchronization with clean hardware separation
|
|
5
|
+
Project-URL: Homepage, https://github.com/murineshiftwork/ttl-barcoder
|
|
6
|
+
Project-URL: Documentation, https://murineshiftwork.github.io/ttl-barcoder/
|
|
7
|
+
Project-URL: Issue Tracker, https://github.com/murineshiftwork/ttl-barcoder/issues
|
|
8
|
+
Author-email: "Lars B. Rollik" <L.B.Rollik@protonmail.com>
|
|
9
|
+
License: BSD-3-Clause
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: barcode,bpod,daq,gpio,neuroscience,pigpio,raspberry-pi,synchronization,ttl
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator
|
|
22
|
+
Classifier: Topic :: System :: Hardware :: Hardware Drivers
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: numpy>=1.20
|
|
25
|
+
Requires-Dist: pydantic>=2.0
|
|
26
|
+
Provides-Extra: bpod
|
|
27
|
+
Requires-Dist: pybpod-api; extra == 'bpod'
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: commitizen; extra == 'dev'
|
|
30
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
31
|
+
Requires-Dist: pre-commit; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
35
|
+
Provides-Extra: docs
|
|
36
|
+
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
37
|
+
Provides-Extra: pigpio
|
|
38
|
+
Requires-Dist: pigpio>=1.78; extra == 'pigpio'
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# TTL Barcoder
|
|
42
|
+
|
|
43
|
+
[](https://pypi.org/project/ttl-barcoder)
|
|
44
|
+
|
|
45
|
+
Generate and decode binary barcodes over TTL signals to synchronize multiple data acquisition systems.
|
|
46
|
+
Barcodes encode a timestamp or random value as a sequence of timed HIGH/LOW pulses, transmittable over any digital output.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from ttl_barcoder.core import BarcodeTTL, BarcodeConfig, TTLType, TimestampPrecision
|
|
53
|
+
|
|
54
|
+
# Timestamp barcode (default) — encodes current time at ms precision
|
|
55
|
+
barcoder = BarcodeTTL()
|
|
56
|
+
sequence = barcoder.get_sequence() # [(level: bool, duration_ms: float), ...]
|
|
57
|
+
|
|
58
|
+
# Random barcode
|
|
59
|
+
config = BarcodeConfig(ttl_type=TTLType.random, barcode_bits=32)
|
|
60
|
+
barcoder = BarcodeTTL(config)
|
|
61
|
+
sequence = barcoder.get_sequence()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Bpod
|
|
65
|
+
```python
|
|
66
|
+
from ttl_barcoder.core import BarcodeTTL
|
|
67
|
+
from ttl_barcoder.hardware.bpod import inject_barcode_states
|
|
68
|
+
|
|
69
|
+
barcoder = BarcodeTTL()
|
|
70
|
+
sequence = barcoder.get_sequence()
|
|
71
|
+
inject_barcode_states(sma, sequence, bnc_channel='BNC1', first_state_name='send_sync', last_state_name='next')
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Raspberry Pi GPIO
|
|
75
|
+
```python
|
|
76
|
+
from ttl_barcoder.hardware.pigpio import send_barcode_sequence
|
|
77
|
+
|
|
78
|
+
send_barcode_sequence(barcoder.get_sequence(), pin=18)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
## Installation
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install ttl-barcoder # core only
|
|
86
|
+
pip install ttl-barcoder[bpod] # + Bpod
|
|
87
|
+
pip install ttl-barcoder[pigpio] # + Raspberry Pi GPIO
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
## Configuration
|
|
92
|
+
|
|
93
|
+
`BarcodeConfig` is a Pydantic model — all fields are validated on construction.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from ttl_barcoder.core import BarcodeConfig, TTLType, TimestampPrecision
|
|
97
|
+
|
|
98
|
+
config = BarcodeConfig(
|
|
99
|
+
ttl_type=TTLType.timestamp, # or TTLType.random
|
|
100
|
+
barcode_bits=37, # 16–64 bits
|
|
101
|
+
timestamp_precision=TimestampPrecision.milliseconds, # s / ms / us
|
|
102
|
+
bit_duration_ms=35.0,
|
|
103
|
+
init_duration_ms=10.0,
|
|
104
|
+
tolerance=0.25,
|
|
105
|
+
)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Presets**: `default`, `high_speed`, `conservative`, `high_precision`, `random`
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from ttl_barcoder.core import get_preset
|
|
112
|
+
config = get_preset("conservative")
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
| Preset | Bits | Precision | Bit duration | TX duration | Coverage |
|
|
116
|
+
|------------------|------|-----------|--------------|-------------|----------|
|
|
117
|
+
| `default` | 37 | ms | 35 ms | 1355 ms | 4.4 yr |
|
|
118
|
+
| `high_speed` | 32 | ms | 25 ms | 848 ms | 49 days |
|
|
119
|
+
| `conservative` | 37 | ms | 50 ms | 1940 ms | 4.4 yr |
|
|
120
|
+
| `high_precision` | 42 | us | 50 ms | 2190 ms | 51 days |
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
## Architecture
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
ttl_barcoder/
|
|
127
|
+
├── core/
|
|
128
|
+
│ ├── config.py # BarcodeConfig (Pydantic), TTLType, TimestampPrecision
|
|
129
|
+
│ ├── generator.py # TTLGenerator ABC → TimestampGenerator / RandomGenerator
|
|
130
|
+
│ ├── encoder.py # bits → (level, duration_ms) timing sequence
|
|
131
|
+
│ ├── decoder.py # edge timestamps → barcode value
|
|
132
|
+
│ └── barcode_ttl.py # BarcodeTTL — main interface combining the above
|
|
133
|
+
└── hardware/
|
|
134
|
+
├── bpod/ # Bpod StateMachine integration
|
|
135
|
+
└── pigpio/ # Raspberry Pi GPIO via pigpio
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- The generator is selected via a factory (`create_generator(config)`) based on `TTLType`.
|
|
139
|
+
- `TimestampGenerator` quantizes Unix time at the configured precision
|
|
140
|
+
- `RandomGenerator` draws from a numpy RNG. Both share the same `encode_bits` / `max_value` interface on the `TTLGenerator` base class.
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
## Examples
|
|
144
|
+
|
|
145
|
+
- `examples/dry_simulation.py` — full walkthrough, no hardware needed
|
|
146
|
+
- `examples/bpod_loopback.py` — Bpod StateMachine with loopback test
|
|
147
|
+
- `examples/pigpio_send.py` — Raspberry Pi GPIO transmission
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
## Contributing
|
|
151
|
+
|
|
152
|
+
1. Fork and create a feature branch
|
|
153
|
+
2. Add tests for new functionality
|
|
154
|
+
3. Run `pytest`
|
|
155
|
+
4. Submit a pull request
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
## Acknowledgments
|
|
159
|
+
|
|
160
|
+
- Based on barcode synchronization from University of Colorado ONE Core
|
|
161
|
+
- Inspired by Open Ephys protocols
|
|
162
|
+
- Built for the neuroscience and scientific DAQ community
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
## License & sources
|
|
166
|
+
|
|
167
|
+
This software is released under the **[BSD 3-Clause License](LICENSE)**.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# TTL Barcoder
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/ttl-barcoder)
|
|
4
|
+
|
|
5
|
+
Generate and decode binary barcodes over TTL signals to synchronize multiple data acquisition systems.
|
|
6
|
+
Barcodes encode a timestamp or random value as a sequence of timed HIGH/LOW pulses, transmittable over any digital output.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from ttl_barcoder.core import BarcodeTTL, BarcodeConfig, TTLType, TimestampPrecision
|
|
13
|
+
|
|
14
|
+
# Timestamp barcode (default) — encodes current time at ms precision
|
|
15
|
+
barcoder = BarcodeTTL()
|
|
16
|
+
sequence = barcoder.get_sequence() # [(level: bool, duration_ms: float), ...]
|
|
17
|
+
|
|
18
|
+
# Random barcode
|
|
19
|
+
config = BarcodeConfig(ttl_type=TTLType.random, barcode_bits=32)
|
|
20
|
+
barcoder = BarcodeTTL(config)
|
|
21
|
+
sequence = barcoder.get_sequence()
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Bpod
|
|
25
|
+
```python
|
|
26
|
+
from ttl_barcoder.core import BarcodeTTL
|
|
27
|
+
from ttl_barcoder.hardware.bpod import inject_barcode_states
|
|
28
|
+
|
|
29
|
+
barcoder = BarcodeTTL()
|
|
30
|
+
sequence = barcoder.get_sequence()
|
|
31
|
+
inject_barcode_states(sma, sequence, bnc_channel='BNC1', first_state_name='send_sync', last_state_name='next')
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Raspberry Pi GPIO
|
|
35
|
+
```python
|
|
36
|
+
from ttl_barcoder.hardware.pigpio import send_barcode_sequence
|
|
37
|
+
|
|
38
|
+
send_barcode_sequence(barcoder.get_sequence(), pin=18)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install ttl-barcoder # core only
|
|
46
|
+
pip install ttl-barcoder[bpod] # + Bpod
|
|
47
|
+
pip install ttl-barcoder[pigpio] # + Raspberry Pi GPIO
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## Configuration
|
|
52
|
+
|
|
53
|
+
`BarcodeConfig` is a Pydantic model — all fields are validated on construction.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from ttl_barcoder.core import BarcodeConfig, TTLType, TimestampPrecision
|
|
57
|
+
|
|
58
|
+
config = BarcodeConfig(
|
|
59
|
+
ttl_type=TTLType.timestamp, # or TTLType.random
|
|
60
|
+
barcode_bits=37, # 16–64 bits
|
|
61
|
+
timestamp_precision=TimestampPrecision.milliseconds, # s / ms / us
|
|
62
|
+
bit_duration_ms=35.0,
|
|
63
|
+
init_duration_ms=10.0,
|
|
64
|
+
tolerance=0.25,
|
|
65
|
+
)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Presets**: `default`, `high_speed`, `conservative`, `high_precision`, `random`
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from ttl_barcoder.core import get_preset
|
|
72
|
+
config = get_preset("conservative")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
| Preset | Bits | Precision | Bit duration | TX duration | Coverage |
|
|
76
|
+
|------------------|------|-----------|--------------|-------------|----------|
|
|
77
|
+
| `default` | 37 | ms | 35 ms | 1355 ms | 4.4 yr |
|
|
78
|
+
| `high_speed` | 32 | ms | 25 ms | 848 ms | 49 days |
|
|
79
|
+
| `conservative` | 37 | ms | 50 ms | 1940 ms | 4.4 yr |
|
|
80
|
+
| `high_precision` | 42 | us | 50 ms | 2190 ms | 51 days |
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
## Architecture
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
ttl_barcoder/
|
|
87
|
+
├── core/
|
|
88
|
+
│ ├── config.py # BarcodeConfig (Pydantic), TTLType, TimestampPrecision
|
|
89
|
+
│ ├── generator.py # TTLGenerator ABC → TimestampGenerator / RandomGenerator
|
|
90
|
+
│ ├── encoder.py # bits → (level, duration_ms) timing sequence
|
|
91
|
+
│ ├── decoder.py # edge timestamps → barcode value
|
|
92
|
+
│ └── barcode_ttl.py # BarcodeTTL — main interface combining the above
|
|
93
|
+
└── hardware/
|
|
94
|
+
├── bpod/ # Bpod StateMachine integration
|
|
95
|
+
└── pigpio/ # Raspberry Pi GPIO via pigpio
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- The generator is selected via a factory (`create_generator(config)`) based on `TTLType`.
|
|
99
|
+
- `TimestampGenerator` quantizes Unix time at the configured precision
|
|
100
|
+
- `RandomGenerator` draws from a numpy RNG. Both share the same `encode_bits` / `max_value` interface on the `TTLGenerator` base class.
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
## Examples
|
|
104
|
+
|
|
105
|
+
- `examples/dry_simulation.py` — full walkthrough, no hardware needed
|
|
106
|
+
- `examples/bpod_loopback.py` — Bpod StateMachine with loopback test
|
|
107
|
+
- `examples/pigpio_send.py` — Raspberry Pi GPIO transmission
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
## Contributing
|
|
111
|
+
|
|
112
|
+
1. Fork and create a feature branch
|
|
113
|
+
2. Add tests for new functionality
|
|
114
|
+
3. Run `pytest`
|
|
115
|
+
4. Submit a pull request
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
## Acknowledgments
|
|
119
|
+
|
|
120
|
+
- Based on barcode synchronization from University of Colorado ONE Core
|
|
121
|
+
- Inspired by Open Ephys protocols
|
|
122
|
+
- Built for the neuroscience and scientific DAQ community
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
## License & sources
|
|
126
|
+
|
|
127
|
+
This software is released under the **[BSD 3-Clause License](LICENSE)**.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.4.1
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Example: Bpod loopback test
|
|
4
|
+
|
|
5
|
+
Demonstrates Bpod StateMachine integration with loopback testing
|
|
6
|
+
on the same device (BNC1 out -> BNC1 in).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from ttl_barcoder.core import BarcodeConfig, BarcodeTTL
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
print("TTL Barcoder - Bpod Loopback Example")
|
|
14
|
+
print("=" * 45)
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from pybpod import StateMachine
|
|
18
|
+
|
|
19
|
+
from ttl_barcoder.hardware.bpod import BpodBarcodeSender, inject_barcode_states
|
|
20
|
+
except ImportError as e:
|
|
21
|
+
print(f"Bpod not available: {e}")
|
|
22
|
+
print("Install with: pip install pybpod")
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
# Configuration for demo
|
|
26
|
+
config = BarcodeConfig(
|
|
27
|
+
barcode_bits=32, # Shorter for demo
|
|
28
|
+
bit_duration_ms=50.0, # Slower for visualization
|
|
29
|
+
init_duration_ms=15.0,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
print(f"Using config: {config}")
|
|
33
|
+
|
|
34
|
+
# Create barcode system
|
|
35
|
+
barcoder = BarcodeTTL(config)
|
|
36
|
+
|
|
37
|
+
# Get barcode sequence
|
|
38
|
+
test_barcode = 12345
|
|
39
|
+
timing_sequence = barcoder.get_sequence(test_barcode)
|
|
40
|
+
|
|
41
|
+
print(f"\nGenerated sequence for barcode {test_barcode}:")
|
|
42
|
+
print(f" {len(timing_sequence)} segments")
|
|
43
|
+
print(f" Total duration: {config.total_duration_ms:.0f}ms")
|
|
44
|
+
|
|
45
|
+
# Create StateMachine
|
|
46
|
+
sma = StateMachine()
|
|
47
|
+
|
|
48
|
+
# Initial state
|
|
49
|
+
sma.add_state(
|
|
50
|
+
state_name="start",
|
|
51
|
+
state_timer=1.0,
|
|
52
|
+
state_change_conditions={"Tup": "send_barcode"},
|
|
53
|
+
output_actions=[],
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Method 1: Direct injection (recommended)
|
|
57
|
+
print("\nMethod 1: Direct injection")
|
|
58
|
+
sender = BpodBarcodeSender()
|
|
59
|
+
sender.inject_states(
|
|
60
|
+
sma=sma,
|
|
61
|
+
timing_sequence=timing_sequence,
|
|
62
|
+
bnc_channel="BNC1",
|
|
63
|
+
first_state_name="send_barcode",
|
|
64
|
+
last_state_name="listen_barcode",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Listen for barcode return
|
|
68
|
+
sma.add_state(
|
|
69
|
+
state_name="listen_barcode",
|
|
70
|
+
state_timer=config.total_duration_ms / 1000.0 + 0.5, # Give extra time
|
|
71
|
+
state_change_conditions={"BNC1High": "detected", "Tup": "timeout"},
|
|
72
|
+
output_actions=[],
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Detection states
|
|
76
|
+
sma.add_state(
|
|
77
|
+
state_name="detected",
|
|
78
|
+
state_timer=0.1,
|
|
79
|
+
state_change_conditions={"Tup": "success"},
|
|
80
|
+
output_actions=[("LED", 255)], # Success indicator
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
sma.add_state(
|
|
84
|
+
state_name="timeout",
|
|
85
|
+
state_timer=0.1,
|
|
86
|
+
state_change_conditions={"Tup": "exit"},
|
|
87
|
+
output_actions=[],
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
sma.add_state(
|
|
91
|
+
state_name="success",
|
|
92
|
+
state_timer=2.0,
|
|
93
|
+
state_change_conditions={"Tup": "exit"},
|
|
94
|
+
output_actions={"LED": 255},
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
print(f"Created StateMachine with {len(sma.state_names)} states")
|
|
98
|
+
print("State sequence:", " -> ".join(sma.state_names))
|
|
99
|
+
|
|
100
|
+
# Method 2: Convenience function
|
|
101
|
+
print("\nMethod 2: Convenience function")
|
|
102
|
+
sma2 = StateMachine()
|
|
103
|
+
sma2.add_state(
|
|
104
|
+
state_name="start",
|
|
105
|
+
state_timer=1.0,
|
|
106
|
+
state_change_conditions={"Tup": "barcode"},
|
|
107
|
+
output_actions=[],
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
inject_barcode_states(
|
|
111
|
+
sma=sma2,
|
|
112
|
+
timing_sequence=timing_sequence,
|
|
113
|
+
bnc_channel="BNC1",
|
|
114
|
+
first_state_name="barcode",
|
|
115
|
+
last_state_name="end",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
sma2.add_state(
|
|
119
|
+
state_name="end",
|
|
120
|
+
state_timer=0.1,
|
|
121
|
+
state_change_conditions={"Tup": "exit"},
|
|
122
|
+
output_actions=[],
|
|
123
|
+
)
|
|
124
|
+
print(f"Method 2 StateMachine: {len(sma2.state_names)} states")
|
|
125
|
+
|
|
126
|
+
# Connection and execution (commented for safety)
|
|
127
|
+
print("\nTo run on actual Bpod device:")
|
|
128
|
+
print("1. Connect Bpod at /dev/ttyACM0")
|
|
129
|
+
print("2. Wire BNC1 output to BNC1 input (loopback)")
|
|
130
|
+
print("3. Uncomment execution code below")
|
|
131
|
+
|
|
132
|
+
"""
|
|
133
|
+
# Uncomment to run on actual device
|
|
134
|
+
class BpodConnection:
|
|
135
|
+
def __init__(self, device_path):
|
|
136
|
+
self.bpod = BpodBase(device_path)
|
|
137
|
+
|
|
138
|
+
def __enter__(self):
|
|
139
|
+
return self
|
|
140
|
+
|
|
141
|
+
def __exit__(self, *_):
|
|
142
|
+
self.bpod.close()
|
|
143
|
+
|
|
144
|
+
def run(self, sma):
|
|
145
|
+
self.bpod.send_state_machine(sma)
|
|
146
|
+
self.bpod.run_state_machine(sma)
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
with BpodConnection("/dev/ttyACM0") as bpod:
|
|
150
|
+
print("Connected to Bpod, running state machine...")
|
|
151
|
+
bpod.run(sma)
|
|
152
|
+
print("StateMachine completed successfully!")
|
|
153
|
+
except Exception as e:
|
|
154
|
+
print(f"Bpod execution failed: {e}")
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
print("\nBpod loopback example completed!")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
if __name__ == "__main__":
|
|
161
|
+
main()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from ttl_barcoder.core import BarcodeConfig, BarcodeTTL, get_preset
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def main():
|
|
5
|
+
print("TTL Barcoder - Dry Simulation Example")
|
|
6
|
+
print("=" * 45)
|
|
7
|
+
|
|
8
|
+
# 1. Basic configuration
|
|
9
|
+
print("\n1. Basic Configuration:")
|
|
10
|
+
config = BarcodeConfig.default()
|
|
11
|
+
print(f"Default config: {config}")
|
|
12
|
+
print(f"Config info: {config.info()}")
|
|
13
|
+
|
|
14
|
+
# 2. Create BarcodeTTL instance
|
|
15
|
+
barcoder = BarcodeTTL(config)
|
|
16
|
+
print(f"\nBarcoder: {barcoder}")
|
|
17
|
+
|
|
18
|
+
# 3. One-liner sequence generation
|
|
19
|
+
print("\n2. One-liner Sequence Generation:")
|
|
20
|
+
sequence = barcoder.get_sequence() # Current timestamp
|
|
21
|
+
print(f"Generated sequence: {len(sequence)} segments")
|
|
22
|
+
|
|
23
|
+
# Show first few segments
|
|
24
|
+
print("First 5 segments:")
|
|
25
|
+
for i, (level, duration) in enumerate(sequence[:5]):
|
|
26
|
+
level_str = "HIGH" if level else "LOW"
|
|
27
|
+
print(f" {i}: {level_str} for {duration:.1f}ms")
|
|
28
|
+
|
|
29
|
+
# 4. Specific barcode
|
|
30
|
+
print("\n3. Specific Barcode:")
|
|
31
|
+
specific_sequence = barcoder.get_sequence(12345)
|
|
32
|
+
print(f"Barcode 12345 sequence: {len(specific_sequence)} segments")
|
|
33
|
+
|
|
34
|
+
# 5. Multiple sequences
|
|
35
|
+
print("\n4. Multiple Sequences:")
|
|
36
|
+
multi_sequences = barcoder.get_multiple_sequences(count=3, interval_s=1.0)
|
|
37
|
+
print(f"Generated {len(multi_sequences)} sequences")
|
|
38
|
+
|
|
39
|
+
# 6. Test different configurations
|
|
40
|
+
print("\n5. Configuration Presets:")
|
|
41
|
+
for preset_name in ["high_speed", "conservative", "high_precision"]:
|
|
42
|
+
preset_config = get_preset(preset_name)
|
|
43
|
+
preset_barcoder = BarcodeTTL(preset_config)
|
|
44
|
+
preset_sequence = preset_barcoder.get_sequence()
|
|
45
|
+
|
|
46
|
+
print(
|
|
47
|
+
f"{preset_name:15s}: {len(preset_sequence)} segments, "
|
|
48
|
+
f"{preset_config.total_duration_ms:.0f}ms total"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# 7. Simulate decode test
|
|
52
|
+
print("\n6. Decode Simulation:")
|
|
53
|
+
# Simulate perfect edge detection
|
|
54
|
+
test_sequence = barcoder.get_sequence(99999)
|
|
55
|
+
simulated_edges = simulate_perfect_edges(test_sequence)
|
|
56
|
+
|
|
57
|
+
decoded = barcoder.decode_edges(*simulated_edges)
|
|
58
|
+
if decoded:
|
|
59
|
+
timestamp, barcode_value = decoded
|
|
60
|
+
print(f"Simulated decode successful: barcode = {barcode_value}")
|
|
61
|
+
else:
|
|
62
|
+
print("Simulated decode failed")
|
|
63
|
+
|
|
64
|
+
print("\nDry simulation completed!")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def simulate_perfect_edges(timing_sequence):
|
|
68
|
+
"""Convert timing sequence to perfect edge timestamps for testing."""
|
|
69
|
+
edge_times = []
|
|
70
|
+
edge_levels = []
|
|
71
|
+
|
|
72
|
+
current_time = 0.0
|
|
73
|
+
current_level = False # Start LOW
|
|
74
|
+
|
|
75
|
+
for target_level, duration_ms in timing_sequence:
|
|
76
|
+
if target_level != current_level:
|
|
77
|
+
# Level change - record edge
|
|
78
|
+
edge_times.append(current_time)
|
|
79
|
+
edge_levels.append(target_level)
|
|
80
|
+
current_level = target_level
|
|
81
|
+
|
|
82
|
+
current_time += duration_ms / 1000.0 # Convert to seconds
|
|
83
|
+
|
|
84
|
+
return edge_times, edge_levels
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
main()
|