reacher2p 3.0.0b4__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.
- reacher2p-3.0.0b4/LICENSE +24 -0
- reacher2p-3.0.0b4/PKG-INFO +436 -0
- reacher2p-3.0.0b4/README.md +399 -0
- reacher2p-3.0.0b4/pyproject.toml +72 -0
- reacher2p-3.0.0b4/setup.cfg +4 -0
- reacher2p-3.0.0b4/src/reacher/__init__.py +22 -0
- reacher2p-3.0.0b4/src/reacher/__main__.py +6 -0
- reacher2p-3.0.0b4/src/reacher/api/__init__.py +0 -0
- reacher2p-3.0.0b4/src/reacher/api/app.py +339 -0
- reacher2p-3.0.0b4/src/reacher/api/middleware/__init__.py +0 -0
- reacher2p-3.0.0b4/src/reacher/api/middleware/auth.py +74 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/__init__.py +0 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/data.py +78 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/discovery.py +348 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/file.py +344 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/firmware.py +193 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/hardware.py +241 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/lifecycle.py +42 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/pairing.py +97 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/program.py +187 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/proxy.py +212 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/serial.py +164 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/session.py +93 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/update.py +200 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/validate.py +25 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/validators.py +382 -0
- reacher2p-3.0.0b4/src/reacher/api/routers/websocket.py +406 -0
- reacher2p-3.0.0b4/src/reacher/device_id.py +33 -0
- reacher2p-3.0.0b4/src/reacher/discovery.py +291 -0
- reacher2p-3.0.0b4/src/reacher/hex/mega/fr.hex +2154 -0
- reacher2p-3.0.0b4/src/reacher/hex/mega/omission.hex +2086 -0
- reacher2p-3.0.0b4/src/reacher/hex/mega/pavlovian.hex +2220 -0
- reacher2p-3.0.0b4/src/reacher/hex/mega/pr.hex +2166 -0
- reacher2p-3.0.0b4/src/reacher/hex/mega/vi.hex +2138 -0
- reacher2p-3.0.0b4/src/reacher/hex/uno/fr.hex +1866 -0
- reacher2p-3.0.0b4/src/reacher/hex/uno/omission.hex +1803 -0
- reacher2p-3.0.0b4/src/reacher/hex/uno/pavlovian.hex +2011 -0
- reacher2p-3.0.0b4/src/reacher/hex/uno/pr.hex +1877 -0
- reacher2p-3.0.0b4/src/reacher/hex/uno/vi.hex +1848 -0
- reacher2p-3.0.0b4/src/reacher/kernel/__init__.py +19 -0
- reacher2p-3.0.0b4/src/reacher/kernel/commands.py +741 -0
- reacher2p-3.0.0b4/src/reacher/kernel/reacher.py +1548 -0
- reacher2p-3.0.0b4/src/reacher/kernel/simulator.py +650 -0
- reacher2p-3.0.0b4/src/reacher/machines.py +68 -0
- reacher2p-3.0.0b4/src/reacher/monitor.py +324 -0
- reacher2p-3.0.0b4/src/reacher/pairing.py +180 -0
- reacher2p-3.0.0b4/src/reacher/pin_overrides.py +233 -0
- reacher2p-3.0.0b4/src/reacher/session_manager.py +216 -0
- reacher2p-3.0.0b4/src/reacher/uploader/__init__.py +12 -0
- reacher2p-3.0.0b4/src/reacher/uploader/boards.py +83 -0
- reacher2p-3.0.0b4/src/reacher/uploader/uploader.py +411 -0
- reacher2p-3.0.0b4/src/reacher2p.egg-info/PKG-INFO +436 -0
- reacher2p-3.0.0b4/src/reacher2p.egg-info/SOURCES.txt +62 -0
- reacher2p-3.0.0b4/src/reacher2p.egg-info/dependency_links.txt +1 -0
- reacher2p-3.0.0b4/src/reacher2p.egg-info/entry_points.txt +3 -0
- reacher2p-3.0.0b4/src/reacher2p.egg-info/requires.txt +17 -0
- reacher2p-3.0.0b4/src/reacher2p.egg-info/top_level.txt +1 -0
- reacher2p-3.0.0b4/tests/test_api.py +727 -0
- reacher2p-3.0.0b4/tests/test_command_parity.py +84 -0
- reacher2p-3.0.0b4/tests/test_commands.py +171 -0
- reacher2p-3.0.0b4/tests/test_pin_overrides.py +181 -0
- reacher2p-3.0.0b4/tests/test_session_manager.py +117 -0
- reacher2p-3.0.0b4/tests/test_validate.py +561 -0
- reacher2p-3.0.0b4/tests/test_websocket.py +270 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to <https://unlicense.org>
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reacher2p
|
|
3
|
+
Version: 3.0.0b4
|
|
4
|
+
Summary: REACHER — Rodent Experiment Application Controls and Handling Ecosystem for Research
|
|
5
|
+
Author-email: Joshua Boquiren <thejoshbq@proton.me>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Otis-Lab-MUSC/REACHER
|
|
8
|
+
Project-URL: Repository, https://github.com/Otis-Lab-MUSC/reacher
|
|
9
|
+
Project-URL: Issues, https://github.com/Otis-Lab-MUSC/reacher/issues
|
|
10
|
+
Keywords: neuroscience,arduino,operant-conditioning,experiment-control,fastapi
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: pyserial<4.0,>=3.5
|
|
22
|
+
Requires-Dist: fastapi<1.0,>=0.110
|
|
23
|
+
Requires-Dist: uvicorn[standard]<1.0,>=0.29
|
|
24
|
+
Requires-Dist: websockets<15.0,>=12.0
|
|
25
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
26
|
+
Requires-Dist: zeroconf<1.0,>=0.131
|
|
27
|
+
Requires-Dist: rich<15.0,>=13.0
|
|
28
|
+
Provides-Extra: tray
|
|
29
|
+
Requires-Dist: pystray>=0.19; extra == "tray"
|
|
30
|
+
Requires-Dist: Pillow>=10.0; extra == "tray"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-mock>=3.12; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# REACHER — Python Backend
|
|
39
|
+
|
|
40
|
+
**FastAPI server, serial communication kernel, and session manager for the REACHER ecosystem**
|
|
41
|
+
|
|
42
|
+
[](https://github.com/Otis-Lab-MUSC/reacher/releases)
|
|
43
|
+
[](https://www.python.org)
|
|
44
|
+
[](LICENSE)
|
|
45
|
+
[](CHANGELOG.md)
|
|
46
|
+
[](https://github.com/Otis-Lab-MUSC)
|
|
47
|
+
|
|
48
|
+
*Written by*: Joshua Boquiren
|
|
49
|
+
|
|
50
|
+
[](https://github.com/thejoshbq)
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Overview
|
|
55
|
+
|
|
56
|
+
The Python backend is the core of the REACHER system. It provides:
|
|
57
|
+
|
|
58
|
+
- A **FastAPI REST API** for session management, hardware control, experiment execution, and data export
|
|
59
|
+
- A **WebSocket server** for real-time event streaming to the browser UI
|
|
60
|
+
- A **multi-threaded serial communication kernel** for bidirectional JSON messaging with Arduino hardware
|
|
61
|
+
- A **session manager** coordinating multiple simultaneous experiment sessions with port locking
|
|
62
|
+
- A **firmware uploader** for flashing Arduino `.hex` files via `avrdude`
|
|
63
|
+
|
|
64
|
+
When running as a standalone executable, the backend also serves the React frontend as static files and opens a browser window automatically.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Role in the REACHER Ecosystem
|
|
69
|
+
|
|
70
|
+
The Python backend is the bridge between the Arduino hardware and the browser-based UI. It:
|
|
71
|
+
|
|
72
|
+
1. Communicates with one or more Arduinos over USB serial (115200 baud, JSON messages)
|
|
73
|
+
2. Exposes a REST API and WebSocket endpoint on port 6229
|
|
74
|
+
3. Serves the React frontend as static files at the root URL
|
|
75
|
+
4. Manages experiment sessions — starting, stopping, pausing, and collecting data
|
|
76
|
+
5. Handles firmware uploads to Arduino boards via `avrdude`
|
|
77
|
+
6. Logs all serial events and behavioral data for post-experiment analysis
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Arduino ◄──USB Serial──► REACHER Kernel ◄──► FastAPI ◄──► React Frontend
|
|
81
|
+
(threads) (REST+WS) (browser)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Architecture
|
|
87
|
+
|
|
88
|
+
### Project Structure
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
reacher/
|
|
92
|
+
├── pyproject.toml # Package metadata and dependencies
|
|
93
|
+
├── src/reacher/
|
|
94
|
+
│ ├── __init__.py # Exports: REACHER, COMMAND_REGISTRY, CommandCode, PARADIGMS
|
|
95
|
+
│ ├── __main__.py # Entry point for `python -m reacher`
|
|
96
|
+
│ ├── session_manager.py # Multi-session coordinator with port locking
|
|
97
|
+
│ ├── api/
|
|
98
|
+
│ │ ├── app.py # FastAPI app, CORS, lifespan, static file mount
|
|
99
|
+
│ │ └── routers/
|
|
100
|
+
│ │ ├── session.py # Session CRUD
|
|
101
|
+
│ │ ├── serial.py # Port listing and serial connections
|
|
102
|
+
│ │ ├── firmware.py # Paradigm listing and firmware upload
|
|
103
|
+
│ │ ├── hardware.py # Command dispatch and config retrieval
|
|
104
|
+
│ │ ├── program.py # Start/stop/pause and limit configuration
|
|
105
|
+
│ │ ├── data.py # Behavior events, frames, CSV export
|
|
106
|
+
│ │ ├── file.py # Filename and destination configuration
|
|
107
|
+
│ │ ├── lifecycle.py # Graceful shutdown
|
|
108
|
+
│ │ └── websocket.py # Real-time event streaming
|
|
109
|
+
│ ├── kernel/
|
|
110
|
+
│ │ ├── reacher.py # Core REACHER class (serial I/O, threading, data)
|
|
111
|
+
│ │ └── commands.py # CommandCode enum, CommandSpec, COMMAND_REGISTRY
|
|
112
|
+
│ ├── uploader/
|
|
113
|
+
│ │ └── uploader.py # FirmwareUploader (avrdude wrapper)
|
|
114
|
+
│ └── hex/<board>/*.hex # Committed firmware artifacts (package data, shipped in wheel)
|
|
115
|
+
├── firmware/ # Arduino firmware source (folded in from reacher-firmware)
|
|
116
|
+
│ ├── compile.sh # Builds all 5 paradigms -> ../src/reacher/hex/<board>/
|
|
117
|
+
│ ├── fr/ pr/ vi/ omission/ pavlovian/ # Per-paradigm sketches
|
|
118
|
+
│ └── libraries/REACHERDevices/ # Shared C++ device library + Commands.h
|
|
119
|
+
└── tests/
|
|
120
|
+
├── test_commands.py
|
|
121
|
+
├── test_command_parity.py # Asserts Commands.h matches the CommandCode enum
|
|
122
|
+
├── test_session_manager.py
|
|
123
|
+
├── test_api.py
|
|
124
|
+
└── core/
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Kernel — Multi-Threaded Serial I/O
|
|
128
|
+
|
|
129
|
+
The `REACHER` class manages all communication with a single Arduino. Each instance runs three daemon threads:
|
|
130
|
+
|
|
131
|
+
| Thread | Target | Purpose |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| `serial_thread` | `read_serial()` | Continuously reads incoming serial data and queues it |
|
|
134
|
+
| `queue_thread` | `handle_queue()` | Processes queued messages, delegates to event handlers |
|
|
135
|
+
| `time_check_thread` | `monitor_time_limit()` | Enforces time and infusion limits during experiments |
|
|
136
|
+
|
|
137
|
+
Thread coordination uses `threading.Event` flags:
|
|
138
|
+
- `serial_flag` — cleared to read, set to stop
|
|
139
|
+
- `program_flag` — cleared when running, set when paused/stopped
|
|
140
|
+
- `time_check_flag` — monitors limit conditions
|
|
141
|
+
|
|
142
|
+
### Session Manager
|
|
143
|
+
|
|
144
|
+
The `SessionManager` coordinates multiple independent `REACHER` instances:
|
|
145
|
+
|
|
146
|
+
- **Port locking** — prevents two sessions from binding to the same COM port
|
|
147
|
+
- **Session states** — `idle` → `uploading` → `connected` → `running` → `paused` → `stopped`
|
|
148
|
+
- **Session IDs** — 12-character hexadecimal identifiers (from `uuid4`)
|
|
149
|
+
- **Event broadcasting** — state changes are forwarded to connected WebSocket clients
|
|
150
|
+
|
|
151
|
+
### Firmware Uploader
|
|
152
|
+
|
|
153
|
+
The `FirmwareUploader` wraps `avrdude` to flash compiled `.hex` files onto the Arduino (Mega 2560, ATmega2560; legacy UNO artifacts still ship):
|
|
154
|
+
|
|
155
|
+
- Async subprocess execution with progress parsing from `avrdude` stderr
|
|
156
|
+
- Hex resolution order: PyInstaller bundle (`_MEIPASS/hex/`) → `REACHER_HEX_DIR` → package data (`src/reacher/hex/`, canonical) → cwd `firmware/hex/` → `~/.reacher/hex` GitHub cache. Set `REACHER_SKIP_HEX_FETCH=1` to disable the network fallback on airgapped hosts.
|
|
157
|
+
|
|
158
|
+
### Firmware Source
|
|
159
|
+
|
|
160
|
+
Firmware source lives in `firmware/` (folded in from the now-archived `Otis-Lab-MUSC/reacher-firmware`). Five Arduino sketches share the `REACHERDevices` C++ library. `firmware/Commands.h` and `kernel/commands.py` are kept in lockstep — `tests/test_command_parity.py` fails on drift.
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
arduino-cli core install arduino:avr # one-time toolchain install
|
|
164
|
+
bash firmware/compile.sh # recompile -> src/reacher/hex/<board>/ (commit the result)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The compiled `hex/<board>/*.hex` files are **committed** package data (`pyproject.toml` glob `hex/**/*.hex`) and ship inside the wheel — there is no firmware build step in CI. Firmware version strings (`library.properties` + each sketch's `SendIdentification()`) are stamped by `scripts/bump-version.py` from the package version; recompile hex after bumping. The microscope timestamp pin (INT0) is fixed in firmware and not remappable.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## API Reference
|
|
172
|
+
|
|
173
|
+
### REST Endpoints
|
|
174
|
+
|
|
175
|
+
#### Sessions (`/api/sessions`)
|
|
176
|
+
|
|
177
|
+
| Method | Path | Description |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
| `GET` | `/api/sessions` | List all active sessions |
|
|
180
|
+
| `POST` | `/api/sessions` | Create a new session (body: `{port, paradigm?}`) |
|
|
181
|
+
| `GET` | `/api/sessions/{id}` | Get session details |
|
|
182
|
+
| `POST` | `/api/sessions/{id}/reset` | Reset a session instance |
|
|
183
|
+
| `DELETE` | `/api/sessions/{id}` | Destroy a session |
|
|
184
|
+
|
|
185
|
+
#### Serial (`/api/serial`)
|
|
186
|
+
|
|
187
|
+
| Method | Path | Description |
|
|
188
|
+
|---|---|---|
|
|
189
|
+
| `GET` | `/api/serial/ports` | List available COM/serial ports |
|
|
190
|
+
| `POST` | `/api/serial/{id}/connect` | Connect session to its serial port |
|
|
191
|
+
| `POST` | `/api/serial/{id}/disconnect` | Disconnect serial |
|
|
192
|
+
|
|
193
|
+
#### Firmware (`/api/firmware`)
|
|
194
|
+
|
|
195
|
+
| Method | Path | Description |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| `GET` | `/api/firmware/paradigms` | List available paradigm hex files |
|
|
198
|
+
| `POST` | `/api/firmware/upload/{id}` | Upload firmware to Arduino (body: `{paradigm}`) |
|
|
199
|
+
|
|
200
|
+
#### Hardware (`/api/hardware`)
|
|
201
|
+
|
|
202
|
+
| Method | Path | Description |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| `POST` | `/api/hardware/{id}/command` | Send command by code (body: `{code, value?}`) |
|
|
205
|
+
| `GET` | `/api/hardware/{id}/commands` | List commands available for the current paradigm |
|
|
206
|
+
| `GET` | `/api/hardware/{id}/config` | Get firmware info and hardware settings |
|
|
207
|
+
|
|
208
|
+
#### Program (`/api/program`)
|
|
209
|
+
|
|
210
|
+
| Method | Path | Description |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| `POST` | `/api/program/{id}/start` | Start the experiment |
|
|
213
|
+
| `POST` | `/api/program/{id}/stop` | Stop the experiment |
|
|
214
|
+
| `POST` | `/api/program/{id}/pause` | Toggle pause/resume |
|
|
215
|
+
| `POST` | `/api/program/{id}/limit` | Set limits (body: `{type, time_limit?, infusion_limit?, delay?}`) |
|
|
216
|
+
|
|
217
|
+
#### Data (`/api/data`)
|
|
218
|
+
|
|
219
|
+
| Method | Path | Description |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| `GET` | `/api/data/{id}/behavior` | Get behavioral events (supports `?since=` for pagination) |
|
|
222
|
+
| `GET` | `/api/data/{id}/frames` | Get frame timestamps |
|
|
223
|
+
| `GET` | `/api/data/{id}/export/csv` | Export behavior data as CSV download |
|
|
224
|
+
|
|
225
|
+
#### File (`/api/file`)
|
|
226
|
+
|
|
227
|
+
| Method | Path | Description |
|
|
228
|
+
|---|---|---|
|
|
229
|
+
| `POST` | `/api/file/{id}/config` | Set output filename and destination (body: `{filename?, destination?}`) |
|
|
230
|
+
| `POST` | `/api/file/{id}/create_folder` | Create data output folder |
|
|
231
|
+
|
|
232
|
+
#### Lifecycle (`/api/lifecycle`)
|
|
233
|
+
|
|
234
|
+
| Method | Path | Description |
|
|
235
|
+
|---|---|---|
|
|
236
|
+
| `POST` | `/api/lifecycle/shutdown` | Graceful shutdown (3-second grace period) |
|
|
237
|
+
|
|
238
|
+
### WebSocket
|
|
239
|
+
|
|
240
|
+
| Endpoint | Description |
|
|
241
|
+
|---|---|
|
|
242
|
+
| `ws://localhost:6229/ws/{session_id}` | Real-time event stream for a session |
|
|
243
|
+
|
|
244
|
+
**Event types sent over WebSocket:**
|
|
245
|
+
|
|
246
|
+
| Type | Description |
|
|
247
|
+
|---|---|
|
|
248
|
+
| `event` | Behavioral event (lever press, pump infusion, lick, etc.) |
|
|
249
|
+
| `frame` | Microscope frame timestamp |
|
|
250
|
+
| `config` | Firmware identification and hardware settings |
|
|
251
|
+
| `upload_progress` | Firmware upload progress (`{percent, stage}`) |
|
|
252
|
+
| `session_state` | Session state change notification |
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Serial Protocol
|
|
257
|
+
|
|
258
|
+
Communication with Arduino hardware uses the following protocol:
|
|
259
|
+
|
|
260
|
+
| Parameter | Value |
|
|
261
|
+
|---|---|
|
|
262
|
+
| Baud rate | 115200 |
|
|
263
|
+
| Encoding | UTF-8 |
|
|
264
|
+
| Message format | Newline-delimited JSON |
|
|
265
|
+
| Identification query | `*IDN?` (SCPI-style) |
|
|
266
|
+
|
|
267
|
+
### Event code meanings (firmware → backend)
|
|
268
|
+
|
|
269
|
+
| Code | Meaning |
|
|
270
|
+
|---|---|
|
|
271
|
+
| `000` | Configuration / firmware identification |
|
|
272
|
+
| `001` | Log messages (arm/disarm state changes) |
|
|
273
|
+
| `006` | Error messages |
|
|
274
|
+
| `007` | Behavioral events (lever presses, pump activations, licks, etc.) |
|
|
275
|
+
| `008` | Microscope frame timestamps |
|
|
276
|
+
|
|
277
|
+
### Command code ranges (backend → firmware)
|
|
278
|
+
|
|
279
|
+
| Range | Target |
|
|
280
|
+
|---|---|
|
|
281
|
+
| 100–105 | Controller (start, stop, identify, pause) |
|
|
282
|
+
| 201–220 | Session setup (ratio, paradigm parameters, Pavlovian settings) |
|
|
283
|
+
| 300–382 | Cue/speaker control (primary and secondary) |
|
|
284
|
+
| 400–482 | Pump control (primary and secondary) |
|
|
285
|
+
| 500–501 | Lick circuit (arm/disarm) |
|
|
286
|
+
| 600–682 | Laser control |
|
|
287
|
+
| 900–903 | Microscope control |
|
|
288
|
+
| 1000–1081 | Right-hand lever control |
|
|
289
|
+
| 1300–1381 | Left-hand lever control |
|
|
290
|
+
|
|
291
|
+
The backend's `COMMAND_REGISTRY` contains 71 `CommandSpec` entries with paradigm filtering — commands are only exposed for paradigms that use them.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Installation
|
|
296
|
+
|
|
297
|
+
### From a wheel file
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
pip install reacher2p-3.0.0b4-py3-none-any.whl
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### From source
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
git clone https://github.com/otis-lab-musc/reacher.git
|
|
307
|
+
pip install -e reacher/
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Running
|
|
313
|
+
|
|
314
|
+
### CLI command
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
reacher
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Module invocation
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
python -m reacher
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Both start the FastAPI server on `http://localhost:6229` and open a browser window. Set the `REACHER_STATIC_DIR` environment variable to point to a built frontend directory, or run from the [labrynth](https://github.com/Otis-Lab-MUSC/labrynth) root where `web/dist/` will be found automatically.
|
|
327
|
+
|
|
328
|
+
### Port configuration
|
|
329
|
+
|
|
330
|
+
Set the `REACHER_PORT` environment variable to change the default port:
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
REACHER_PORT=8080 reacher
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Development
|
|
339
|
+
|
|
340
|
+
### Setting up a development environment
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
git clone https://github.com/otis-lab-musc/reacher.git
|
|
344
|
+
cd reacher
|
|
345
|
+
python -m venv .venv
|
|
346
|
+
source .venv/bin/activate # Linux/macOS
|
|
347
|
+
# .venv\Scripts\activate # Windows
|
|
348
|
+
pip install -e ".[dev]"
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Running tests
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
pytest
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Linting
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
ruff check .
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Building the Standalone Executable
|
|
366
|
+
|
|
367
|
+
See [labrynth](https://github.com/Otis-Lab-MUSC/labrynth) for standalone packaging via PyInstaller.
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Configuration
|
|
372
|
+
|
|
373
|
+
### Environment Variables
|
|
374
|
+
|
|
375
|
+
| Variable | Default | Description |
|
|
376
|
+
|---|---|---|
|
|
377
|
+
| `REACHER_PORT` | `6229` | HTTP/WebSocket server port |
|
|
378
|
+
| `REACHER_STATIC_DIR` | (CWD/web/dist) | Path to built React frontend directory |
|
|
379
|
+
| `REACHER_HEX_DIR` | (CWD/firmware/hex) | Path to pre-compiled firmware hex files |
|
|
380
|
+
| `REACHER_AVRDUDE_PATH` | (system PATH) | Path to `avrdude` binary (set during build/packaging, not runtime) |
|
|
381
|
+
|
|
382
|
+
### Data Directory
|
|
383
|
+
|
|
384
|
+
REACHER stores logs and data under `~/REACHER/`:
|
|
385
|
+
|
|
386
|
+
```
|
|
387
|
+
~/REACHER/
|
|
388
|
+
├── LOG/
|
|
389
|
+
│ └── YYYY-MM-DD_HH-MM-SS/
|
|
390
|
+
│ ├── controller_log.json # JSON events from firmware
|
|
391
|
+
│ └── interface_log.log # Python logging output
|
|
392
|
+
└── DATA/ # Default data export destination
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
The data export destination can be customized per session via the File API.
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Dependencies
|
|
400
|
+
|
|
401
|
+
### Runtime
|
|
402
|
+
|
|
403
|
+
| Package | Version | Purpose |
|
|
404
|
+
|---|---|---|
|
|
405
|
+
| pyserial | ≥3.5 | Serial port communication |
|
|
406
|
+
| fastapi | ≥0.110 | REST API framework |
|
|
407
|
+
| uvicorn[standard] | ≥0.29 | ASGI server |
|
|
408
|
+
| websockets | ≥12.0 | WebSocket protocol support |
|
|
409
|
+
|
|
410
|
+
### Optional (tray extra: `pip install reacher2p[tray]`)
|
|
411
|
+
|
|
412
|
+
| Package | Version | Purpose |
|
|
413
|
+
|---|---|---|
|
|
414
|
+
| pystray | ≥0.19 | System tray icon (standalone mode) |
|
|
415
|
+
| Pillow | ≥10.0 | Image support for tray icon |
|
|
416
|
+
|
|
417
|
+
### Development
|
|
418
|
+
|
|
419
|
+
| Package | Version | Purpose |
|
|
420
|
+
|---|---|---|
|
|
421
|
+
| pytest | ≥8.0 | Test runner |
|
|
422
|
+
| pytest-asyncio | ≥0.23 | Async test support |
|
|
423
|
+
| httpx | ≥0.27 | HTTP test client |
|
|
424
|
+
| ruff | ≥0.4 | Linter and formatter |
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## License
|
|
429
|
+
|
|
430
|
+
This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
|
|
431
|
+
|
|
432
|
+
## Contact
|
|
433
|
+
|
|
434
|
+
Joshua Boquiren — [thejoshbq@proton.me](mailto:thejoshbq@proton.me)
|
|
435
|
+
|
|
436
|
+
[GitHub: otis-lab-musc/reacher](https://github.com/otis-lab-musc/reacher)
|