ephys-link 1.1.1__tar.gz → 1.2.0__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.
- {ephys_link-1.1.1 → ephys_link-1.2.0}/.gitignore +0 -1
- {ephys_link-1.1.1 → ephys_link-1.2.0}/PKG-INFO +62 -45
- {ephys_link-1.1.1 → ephys_link-1.2.0}/README.md +60 -44
- ephys_link-1.2.0/assets/icon.ico +0 -0
- ephys_link-1.2.0/ephys_link.spec +39 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/pyproject.toml +9 -0
- ephys_link-1.2.0/src/ephys_link/__about__.py +1 -0
- ephys_link-1.2.0/src/ephys_link/__main__.py +88 -0
- ephys_link-1.2.0/src/ephys_link/emergency_stop.py +67 -0
- ephys_link-1.2.0/src/ephys_link/gui.py +163 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/server.py +10 -3
- ephys_link-1.1.1/src/ephys_link/__about__.py +0 -1
- ephys_link-1.1.1/src/ephys_link/__main__.py +0 -145
- ephys_link-1.1.1/src/ephys_link/gui.py +0 -167
- {ephys_link-1.1.1 → ephys_link-1.2.0}/LICENSE +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/__init__.py +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/common.py +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platform_handler.py +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platform_manipulator.py +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/__init__.py +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/new_scale_handler.py +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/new_scale_manipulator.py +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/new_scale_pathfinder_handler.py +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/sensapex_handler.py +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/sensapex_manipulator.py +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/ump3_handler.py +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/ump3_manipulator.py +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/resources/CP210xManufacturing.dll +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/resources/NstMotorCtrl.dll +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/resources/SiUSBXp.dll +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/resources/libum.dll +0 -0
- {ephys_link-1.1.1 → ephys_link-1.2.0}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ephys-link
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: A Python Socket.IO server that allows any Socket.IO-compliant application to communicate with manipulators used in electrophysiology experiments.
|
|
5
5
|
Project-URL: Documentation, https://virtualbrainlab.org/ephys_link/installation_and_use.html
|
|
6
6
|
Project-URL: Issues, https://github.com/VirtualBrainLab/ephys-link/issues
|
|
@@ -27,6 +27,7 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
|
27
27
|
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
28
28
|
Requires-Python: <3.13,>=3.8
|
|
29
29
|
Requires-Dist: aiohttp==3.9.1
|
|
30
|
+
Requires-Dist: platformdirs==4.1.0
|
|
30
31
|
Requires-Dist: pyserial==3.5
|
|
31
32
|
Requires-Dist: python-socketio==5.11.0
|
|
32
33
|
Requires-Dist: pythonnet==3.0.3
|
|
@@ -37,6 +38,7 @@ Description-Content-Type: text/markdown
|
|
|
37
38
|
# Electrophysiology Manipulator Link
|
|
38
39
|
|
|
39
40
|
[](https://badge.fury.io/py/ephys-link)
|
|
41
|
+
[](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/build.yml)
|
|
40
42
|
[](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/codeql-analysis.yml)
|
|
41
43
|
[](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/dependency-review.yml)
|
|
42
44
|
[](https://github.com/pypa/hatch)
|
|
@@ -52,10 +54,10 @@ to communicate with manipulators used in electrophysiology experiments.
|
|
|
52
54
|
|
|
53
55
|
**Supported Manipulators:**
|
|
54
56
|
|
|
55
|
-
| Manufacturer | Model
|
|
56
|
-
|
|
57
|
-
| Sensapex | <ul> <li>uMp-4</li> <li>uMp-3</li> </ul>
|
|
58
|
-
| New Scale | <ul> <li>Pathfinder MPM Control
|
|
57
|
+
| Manufacturer | Model |
|
|
58
|
+
|--------------|---------------------------------------------------------------------------|
|
|
59
|
+
| Sensapex | <ul> <li>uMp-4</li> <li>uMp-3</li> </ul> |
|
|
60
|
+
| New Scale | <ul> <li>Pathfinder MPM Control v2.8.8+</li> <li>M3-USB-3:1-EP</li> </ul> |
|
|
59
61
|
|
|
60
62
|
Ephys Link is an open and extensible platform. It is designed to easily support integration with other manipulators.
|
|
61
63
|
|
|
@@ -69,17 +71,12 @@ the [API reference](https://virtualbrainlab.org/api_reference_ephys_link.html).
|
|
|
69
71
|
|
|
70
72
|
## Prerequisites
|
|
71
73
|
|
|
72
|
-
1.
|
|
73
|
-
|
|
74
|
-
of Microsoft Visual C++ (MSVC v143+ x86/64) and the Windows SDK (10/11) to
|
|
75
|
-
be installed. They can be acquired through
|
|
76
|
-
the [Visual Studio Build Tools Installer](https://visualstudio.microsoft.com/visual-cpp-build-tools/).
|
|
77
|
-
2. An **x86 Windows PC is required** to run the server.
|
|
78
|
-
3. For Sensapex devices, the controller unit must be connected via an ethernet
|
|
74
|
+
1. An **x86 Windows PC is required** to run the server.
|
|
75
|
+
2. For Sensapex devices, the controller unit must be connected via an ethernet
|
|
79
76
|
cable and powered. A USB-to-ethernet adapter is acceptable. For New Scale manipulators,
|
|
80
77
|
the controller unit must be connected via USB and be powered by a 6V power
|
|
81
78
|
supply.
|
|
82
|
-
|
|
79
|
+
3. To use the emergency stop feature, ensure an Arduino with
|
|
83
80
|
the [StopSignal](https://github.com/VirtualBrainLab/StopSignal) sketch is
|
|
84
81
|
connected to the computer. Follow the instructions on that repo for how to
|
|
85
82
|
set up the Arduino.
|
|
@@ -88,59 +85,79 @@ the [API reference](https://virtualbrainlab.org/api_reference_ephys_link.html).
|
|
|
88
85
|
is currently designed to interface with local/desktop instances of Pinpoint. It
|
|
89
86
|
will not work with the web browser versions of Pinpoint at this time.
|
|
90
87
|
|
|
91
|
-
|
|
92
|
-
<h3>Using a Python virtual environment is encouraged.</h3>
|
|
93
|
-
<p>Create a virtual environment by running <code>python -m venv ephys_link</code></p>
|
|
94
|
-
<p>Activate the environment by running <code>.\ephys_link\scripts\activate</code></p>
|
|
95
|
-
<p>A virtual environment helps to isolate installed packages from other packages on your computer and ensures a clean installation of Ephys Link</p>
|
|
96
|
-
</div>
|
|
88
|
+
## Install as Standalone Executable
|
|
97
89
|
|
|
98
|
-
|
|
90
|
+
1. Download the latest executable from
|
|
91
|
+
the [releases page](https://github.com/VirtualBrainLab/ephys-link/releases/latest).
|
|
92
|
+
2. Double-click the executable file to launch the configuration window.
|
|
93
|
+
1. Take note of the IP address and port. **Copy this information into Pinpoint to connect**.
|
|
94
|
+
3. Select the desired configuration and click "Launch Server".
|
|
99
95
|
|
|
100
|
-
|
|
96
|
+
The configuration window will close and the server will launch. Your configurations will be saved for future use.
|
|
101
97
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
```
|
|
98
|
+
To connect to the server from Pinpoint, provide the IP address and port. For example, if the server is running on the
|
|
99
|
+
same computer that Pinpoint is, use
|
|
105
100
|
|
|
106
|
-
|
|
101
|
+
- Server: `localhost`
|
|
102
|
+
- Port: `8081`
|
|
107
103
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
```
|
|
104
|
+
If the server is running on a different (local) computer, use the IP address of that computer as shown in the startup
|
|
105
|
+
window instead of `localhost`.
|
|
111
106
|
|
|
112
|
-
## Install for
|
|
107
|
+
## Install for Development
|
|
113
108
|
|
|
114
109
|
1. Clone the repository.
|
|
115
110
|
2. Install [Hatch](https://hatch.pypa.io/latest/install/)
|
|
116
|
-
3.
|
|
111
|
+
3. Install the latest Microsoft Visual C++ (MSVC v143+ x86/64) and the Windows SDK (10/11)
|
|
112
|
+
via [Visual Studio Build Tools Installer](https://visualstudio.microsoft.com/visual-cpp-build-tools/).
|
|
113
|
+
4. In a terminal, navigate to the repository's root directory and run
|
|
117
114
|
|
|
118
115
|
```bash
|
|
119
116
|
hatch shell
|
|
120
117
|
```
|
|
121
118
|
|
|
122
|
-
This will create a virtual environment and install the package in editable mode.
|
|
119
|
+
This will create a virtual environment, install Python 12 (if not found), and install the package in editable mode.
|
|
123
120
|
|
|
124
|
-
|
|
121
|
+
## Install as a Python package
|
|
125
122
|
|
|
126
|
-
|
|
123
|
+
```bash
|
|
124
|
+
pip install ephys-link
|
|
125
|
+
```
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
|--------------------------------------|--------------------------------------|
|
|
130
|
-
| Sensapex uMp-4 | `ephys-link` |
|
|
131
|
-
| Sensapex uMp-3 | `ephys-link -t ump3` |
|
|
132
|
-
| New Scale | `ephys-link -t new_scale` |
|
|
133
|
-
| New Scale via Pathfinder HTTP server | `ephys-link -t new_scale_pathfinder` |
|
|
127
|
+
Import the modules you need and launch the server.
|
|
134
128
|
|
|
135
|
-
|
|
129
|
+
```python
|
|
130
|
+
from ephys_link.server import Server
|
|
136
131
|
|
|
137
|
-
|
|
138
|
-
|
|
132
|
+
server = Server()
|
|
133
|
+
server.launch("sensapex", 8081)
|
|
134
|
+
```
|
|
139
135
|
|
|
140
|
-
|
|
136
|
+
# CLI Usage
|
|
141
137
|
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
Ephys Link can be launched from the command line directly without the configuration window. This is useful for computers
|
|
139
|
+
or servers without graphical user interfaces.
|
|
140
|
+
|
|
141
|
+
With the standalone executable downloaded, invoking the executable from the command line:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
ephys_link-vX.X.X-Windows-x86_64.exe -b
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Use the actual name of the executable you downloaded. The `-b` or `--background` flag will launch the server without the
|
|
148
|
+
configuration window and read configuration from CLI arguments.
|
|
149
|
+
|
|
150
|
+
Run the following commands in a terminal to start the server for the desired manipulator platform without the startup
|
|
151
|
+
window (replace `ephys_link.exe` with the actual name of the executable you downloaded):
|
|
152
|
+
|
|
153
|
+
| Manipulator Platform | Command |
|
|
154
|
+
|------------------------------------------|---------------------------------------------|
|
|
155
|
+
| Sensapex uMp-4 | `ephys_link.exe -b` |
|
|
156
|
+
| Sensapex uMp-3 | `ephys_link.exe -b -t ump3` |
|
|
157
|
+
| New Scale Pathfinder MPM Control v2.8.8+ | `ephys_link.exe -b -t new_scale_pathfinder` |
|
|
158
|
+
| New Scale M3-USB-3:1-EP | `ephys_link.exe -b -t new_scale` |
|
|
159
|
+
|
|
160
|
+
More options can be viewed by running `ephys_link.exe -h`.
|
|
144
161
|
|
|
145
162
|
# Documentation and More Information
|
|
146
163
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Electrophysiology Manipulator Link
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/py/ephys-link)
|
|
4
|
+
[](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/build.yml)
|
|
4
5
|
[](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/codeql-analysis.yml)
|
|
5
6
|
[](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/dependency-review.yml)
|
|
6
7
|
[](https://github.com/pypa/hatch)
|
|
@@ -16,10 +17,10 @@ to communicate with manipulators used in electrophysiology experiments.
|
|
|
16
17
|
|
|
17
18
|
**Supported Manipulators:**
|
|
18
19
|
|
|
19
|
-
| Manufacturer | Model
|
|
20
|
-
|
|
21
|
-
| Sensapex | <ul> <li>uMp-4</li> <li>uMp-3</li> </ul>
|
|
22
|
-
| New Scale | <ul> <li>Pathfinder MPM Control
|
|
20
|
+
| Manufacturer | Model |
|
|
21
|
+
|--------------|---------------------------------------------------------------------------|
|
|
22
|
+
| Sensapex | <ul> <li>uMp-4</li> <li>uMp-3</li> </ul> |
|
|
23
|
+
| New Scale | <ul> <li>Pathfinder MPM Control v2.8.8+</li> <li>M3-USB-3:1-EP</li> </ul> |
|
|
23
24
|
|
|
24
25
|
Ephys Link is an open and extensible platform. It is designed to easily support integration with other manipulators.
|
|
25
26
|
|
|
@@ -33,17 +34,12 @@ the [API reference](https://virtualbrainlab.org/api_reference_ephys_link.html).
|
|
|
33
34
|
|
|
34
35
|
## Prerequisites
|
|
35
36
|
|
|
36
|
-
1.
|
|
37
|
-
|
|
38
|
-
of Microsoft Visual C++ (MSVC v143+ x86/64) and the Windows SDK (10/11) to
|
|
39
|
-
be installed. They can be acquired through
|
|
40
|
-
the [Visual Studio Build Tools Installer](https://visualstudio.microsoft.com/visual-cpp-build-tools/).
|
|
41
|
-
2. An **x86 Windows PC is required** to run the server.
|
|
42
|
-
3. For Sensapex devices, the controller unit must be connected via an ethernet
|
|
37
|
+
1. An **x86 Windows PC is required** to run the server.
|
|
38
|
+
2. For Sensapex devices, the controller unit must be connected via an ethernet
|
|
43
39
|
cable and powered. A USB-to-ethernet adapter is acceptable. For New Scale manipulators,
|
|
44
40
|
the controller unit must be connected via USB and be powered by a 6V power
|
|
45
41
|
supply.
|
|
46
|
-
|
|
42
|
+
3. To use the emergency stop feature, ensure an Arduino with
|
|
47
43
|
the [StopSignal](https://github.com/VirtualBrainLab/StopSignal) sketch is
|
|
48
44
|
connected to the computer. Follow the instructions on that repo for how to
|
|
49
45
|
set up the Arduino.
|
|
@@ -52,59 +48,79 @@ the [API reference](https://virtualbrainlab.org/api_reference_ephys_link.html).
|
|
|
52
48
|
is currently designed to interface with local/desktop instances of Pinpoint. It
|
|
53
49
|
will not work with the web browser versions of Pinpoint at this time.
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
<h3>Using a Python virtual environment is encouraged.</h3>
|
|
57
|
-
<p>Create a virtual environment by running <code>python -m venv ephys_link</code></p>
|
|
58
|
-
<p>Activate the environment by running <code>.\ephys_link\scripts\activate</code></p>
|
|
59
|
-
<p>A virtual environment helps to isolate installed packages from other packages on your computer and ensures a clean installation of Ephys Link</p>
|
|
60
|
-
</div>
|
|
51
|
+
## Install as Standalone Executable
|
|
61
52
|
|
|
62
|
-
|
|
53
|
+
1. Download the latest executable from
|
|
54
|
+
the [releases page](https://github.com/VirtualBrainLab/ephys-link/releases/latest).
|
|
55
|
+
2. Double-click the executable file to launch the configuration window.
|
|
56
|
+
1. Take note of the IP address and port. **Copy this information into Pinpoint to connect**.
|
|
57
|
+
3. Select the desired configuration and click "Launch Server".
|
|
63
58
|
|
|
64
|
-
|
|
59
|
+
The configuration window will close and the server will launch. Your configurations will be saved for future use.
|
|
65
60
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
```
|
|
61
|
+
To connect to the server from Pinpoint, provide the IP address and port. For example, if the server is running on the
|
|
62
|
+
same computer that Pinpoint is, use
|
|
69
63
|
|
|
70
|
-
|
|
64
|
+
- Server: `localhost`
|
|
65
|
+
- Port: `8081`
|
|
71
66
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
```
|
|
67
|
+
If the server is running on a different (local) computer, use the IP address of that computer as shown in the startup
|
|
68
|
+
window instead of `localhost`.
|
|
75
69
|
|
|
76
|
-
## Install for
|
|
70
|
+
## Install for Development
|
|
77
71
|
|
|
78
72
|
1. Clone the repository.
|
|
79
73
|
2. Install [Hatch](https://hatch.pypa.io/latest/install/)
|
|
80
|
-
3.
|
|
74
|
+
3. Install the latest Microsoft Visual C++ (MSVC v143+ x86/64) and the Windows SDK (10/11)
|
|
75
|
+
via [Visual Studio Build Tools Installer](https://visualstudio.microsoft.com/visual-cpp-build-tools/).
|
|
76
|
+
4. In a terminal, navigate to the repository's root directory and run
|
|
81
77
|
|
|
82
78
|
```bash
|
|
83
79
|
hatch shell
|
|
84
80
|
```
|
|
85
81
|
|
|
86
|
-
This will create a virtual environment and install the package in editable mode.
|
|
82
|
+
This will create a virtual environment, install Python 12 (if not found), and install the package in editable mode.
|
|
87
83
|
|
|
88
|
-
|
|
84
|
+
## Install as a Python package
|
|
89
85
|
|
|
90
|
-
|
|
86
|
+
```bash
|
|
87
|
+
pip install ephys-link
|
|
88
|
+
```
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|--------------------------------------|--------------------------------------|
|
|
94
|
-
| Sensapex uMp-4 | `ephys-link` |
|
|
95
|
-
| Sensapex uMp-3 | `ephys-link -t ump3` |
|
|
96
|
-
| New Scale | `ephys-link -t new_scale` |
|
|
97
|
-
| New Scale via Pathfinder HTTP server | `ephys-link -t new_scale_pathfinder` |
|
|
90
|
+
Import the modules you need and launch the server.
|
|
98
91
|
|
|
99
|
-
|
|
92
|
+
```python
|
|
93
|
+
from ephys_link.server import Server
|
|
100
94
|
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
server = Server()
|
|
96
|
+
server.launch("sensapex", 8081)
|
|
97
|
+
```
|
|
103
98
|
|
|
104
|
-
|
|
99
|
+
# CLI Usage
|
|
105
100
|
|
|
106
|
-
|
|
107
|
-
|
|
101
|
+
Ephys Link can be launched from the command line directly without the configuration window. This is useful for computers
|
|
102
|
+
or servers without graphical user interfaces.
|
|
103
|
+
|
|
104
|
+
With the standalone executable downloaded, invoking the executable from the command line:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
ephys_link-vX.X.X-Windows-x86_64.exe -b
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Use the actual name of the executable you downloaded. The `-b` or `--background` flag will launch the server without the
|
|
111
|
+
configuration window and read configuration from CLI arguments.
|
|
112
|
+
|
|
113
|
+
Run the following commands in a terminal to start the server for the desired manipulator platform without the startup
|
|
114
|
+
window (replace `ephys_link.exe` with the actual name of the executable you downloaded):
|
|
115
|
+
|
|
116
|
+
| Manipulator Platform | Command |
|
|
117
|
+
|------------------------------------------|---------------------------------------------|
|
|
118
|
+
| Sensapex uMp-4 | `ephys_link.exe -b` |
|
|
119
|
+
| Sensapex uMp-3 | `ephys_link.exe -b -t ump3` |
|
|
120
|
+
| New Scale Pathfinder MPM Control v2.8.8+ | `ephys_link.exe -b -t new_scale_pathfinder` |
|
|
121
|
+
| New Scale M3-USB-3:1-EP | `ephys_link.exe -b -t new_scale` |
|
|
122
|
+
|
|
123
|
+
More options can be viewed by running `ephys_link.exe -h`.
|
|
108
124
|
|
|
109
125
|
# Documentation and More Information
|
|
110
126
|
|
|
Binary file
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# -*- mode: python ; coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from ephys_link.__about__ import __version__ as version
|
|
4
|
+
|
|
5
|
+
a = Analysis(
|
|
6
|
+
['src\\ephys_link\\__main__.py'],
|
|
7
|
+
pathex=[],
|
|
8
|
+
binaries=[('src\\ephys_link\\resources', 'ephys_link\\resources')],
|
|
9
|
+
datas=[],
|
|
10
|
+
hiddenimports=['engineio.async_drivers.aiohttp', 'engineio.async_aiohttp'],
|
|
11
|
+
hookspath=[],
|
|
12
|
+
hooksconfig={},
|
|
13
|
+
runtime_hooks=[],
|
|
14
|
+
excludes=[],
|
|
15
|
+
noarchive=False,
|
|
16
|
+
)
|
|
17
|
+
pyz = PYZ(a.pure)
|
|
18
|
+
|
|
19
|
+
exe = EXE(
|
|
20
|
+
pyz,
|
|
21
|
+
a.scripts,
|
|
22
|
+
a.binaries,
|
|
23
|
+
a.datas,
|
|
24
|
+
[],
|
|
25
|
+
name=f"ephys_link-v{version}-Windows-x86_64",
|
|
26
|
+
debug=False,
|
|
27
|
+
bootloader_ignore_signals=False,
|
|
28
|
+
strip=False,
|
|
29
|
+
upx=True,
|
|
30
|
+
upx_exclude=[],
|
|
31
|
+
runtime_tmpdir=None,
|
|
32
|
+
console=True,
|
|
33
|
+
disable_windowed_traceback=False,
|
|
34
|
+
argv_emulation=False,
|
|
35
|
+
target_arch=None,
|
|
36
|
+
codesign_identity=None,
|
|
37
|
+
entitlements_file=None,
|
|
38
|
+
icon='assets\\icon.ico',
|
|
39
|
+
)
|
|
@@ -31,6 +31,7 @@ classifiers = [
|
|
|
31
31
|
]
|
|
32
32
|
dependencies = [
|
|
33
33
|
"aiohttp==3.9.1",
|
|
34
|
+
"platformdirs==4.1.0",
|
|
34
35
|
"pyserial==3.5",
|
|
35
36
|
"python-socketio==5.11.0",
|
|
36
37
|
"pythonnet==3.0.3",
|
|
@@ -82,6 +83,14 @@ dependencies = [
|
|
|
82
83
|
[tool.hatch.envs.types.scripts]
|
|
83
84
|
check = "mypy --install-types --non-interactive {args:src/ephys_link tests}"
|
|
84
85
|
|
|
86
|
+
[tool.hatch.envs.exe]
|
|
87
|
+
python = "3.12"
|
|
88
|
+
dependencies = [
|
|
89
|
+
"pyinstaller==6.3.0",
|
|
90
|
+
]
|
|
91
|
+
[tool.hatch.envs.exe.scripts]
|
|
92
|
+
build = "pyinstaller.exe ephys_link.spec -y"
|
|
93
|
+
|
|
85
94
|
[tool.coverage.run]
|
|
86
95
|
source_pkgs = ["ephys_link", "tests"]
|
|
87
96
|
branch = true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.2.0"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from argparse import ArgumentParser
|
|
2
|
+
|
|
3
|
+
from ephys_link import common as com
|
|
4
|
+
from ephys_link.__about__ import __version__ as version
|
|
5
|
+
from ephys_link.emergency_stop import EmergencyStop
|
|
6
|
+
from ephys_link.gui import GUI
|
|
7
|
+
from ephys_link.server import Server
|
|
8
|
+
|
|
9
|
+
# Setup argument parser.
|
|
10
|
+
parser = ArgumentParser(
|
|
11
|
+
description="Electrophysiology Manipulator Link: a websocket interface for"
|
|
12
|
+
" manipulators in electrophysiology experiments",
|
|
13
|
+
prog="python -m ephys-link",
|
|
14
|
+
)
|
|
15
|
+
parser.add_argument(
|
|
16
|
+
"-b", "--background", dest="background", action="store_true", help="Launches in headless mode (no GUI)"
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"-t",
|
|
20
|
+
"--type",
|
|
21
|
+
type=str,
|
|
22
|
+
dest="type",
|
|
23
|
+
default="sensapex",
|
|
24
|
+
help='Manipulator type (i.e. "sensapex", "new_scale", or "new_scale_pathfinder").' ' Default: "sensapex"',
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument("-d", "--debug", dest="debug", action="store_true", help="Enable debug mode")
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
"-p",
|
|
29
|
+
"--port",
|
|
30
|
+
type=int,
|
|
31
|
+
default=8081,
|
|
32
|
+
dest="port",
|
|
33
|
+
help="Port to serve on. Default: 8081 (avoids conflict with other HTTP servers)",
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--pathfinder_port",
|
|
37
|
+
type=int,
|
|
38
|
+
default=8080,
|
|
39
|
+
dest="pathfinder_port",
|
|
40
|
+
help="Port New Scale Pathfinder's server is on. Default: 8080",
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"-s",
|
|
44
|
+
"--serial",
|
|
45
|
+
type=str,
|
|
46
|
+
default="no-e-stop",
|
|
47
|
+
dest="serial",
|
|
48
|
+
nargs="?",
|
|
49
|
+
help="Emergency stop serial port (i.e. COM3). Default: disables emergency stop",
|
|
50
|
+
)
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
"-v",
|
|
53
|
+
"--version",
|
|
54
|
+
action="version",
|
|
55
|
+
version=f"Electrophysiology Manipulator Link v{version}",
|
|
56
|
+
help="Print version and exit",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def main() -> None:
|
|
61
|
+
"""Main function"""
|
|
62
|
+
|
|
63
|
+
# Parse arguments.
|
|
64
|
+
args = parser.parse_args()
|
|
65
|
+
|
|
66
|
+
# Launch GUI if not background.
|
|
67
|
+
if not args.background:
|
|
68
|
+
gui = GUI()
|
|
69
|
+
gui.launch()
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
# Otherwise, create Server from CLI.
|
|
73
|
+
server = Server()
|
|
74
|
+
|
|
75
|
+
# Continue with CLI if not.
|
|
76
|
+
com.DEBUG = args.debug
|
|
77
|
+
|
|
78
|
+
# Setup serial port.
|
|
79
|
+
if args.serial != "no-e-stop":
|
|
80
|
+
e_stop = EmergencyStop(server, args.serial)
|
|
81
|
+
e_stop.watch()
|
|
82
|
+
|
|
83
|
+
# Launch with parsed arguments on main thread.
|
|
84
|
+
server.launch(args.type, args.port, args.pathfinder_port)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
main()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from signal import SIGINT, SIGTERM, signal
|
|
2
|
+
from threading import Event, Thread
|
|
3
|
+
from time import sleep
|
|
4
|
+
|
|
5
|
+
from serial import Serial
|
|
6
|
+
from serial.tools import list_ports
|
|
7
|
+
|
|
8
|
+
from ephys_link.common import dprint
|
|
9
|
+
from ephys_link.server import Server
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class EmergencyStop:
|
|
13
|
+
"""Serial system for emergency stop"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, server: Server, serial_port: str) -> None:
|
|
16
|
+
"""Setup serial port for emergency stop
|
|
17
|
+
|
|
18
|
+
:param server: The Ephys Link server instance
|
|
19
|
+
:type server: Server
|
|
20
|
+
:param serial_port: The serial port to poll
|
|
21
|
+
:type serial_port: str
|
|
22
|
+
:return: None
|
|
23
|
+
"""
|
|
24
|
+
self._server = server
|
|
25
|
+
self._serial_port = serial_port
|
|
26
|
+
self._poll_rate = 0.05
|
|
27
|
+
self._kill_serial_event = Event()
|
|
28
|
+
self._poll_serial_thread = Thread(target=self._poll_serial, daemon=True)
|
|
29
|
+
|
|
30
|
+
# Register signals
|
|
31
|
+
signal(SIGTERM, self._close_serial)
|
|
32
|
+
signal(SIGINT, self._close_serial)
|
|
33
|
+
|
|
34
|
+
def watch(self) -> None:
|
|
35
|
+
"""Start polling serial port for emergency stop"""
|
|
36
|
+
self._poll_serial_thread.start()
|
|
37
|
+
|
|
38
|
+
def _poll_serial(self) -> None:
|
|
39
|
+
"""Continuously poll serial port for data
|
|
40
|
+
|
|
41
|
+
Close port on kill event
|
|
42
|
+
"""
|
|
43
|
+
target_port = self._serial_port
|
|
44
|
+
if self._serial_port is None:
|
|
45
|
+
# Search for serial ports
|
|
46
|
+
for port, desc, _ in list_ports.comports():
|
|
47
|
+
if "Arduino" in desc or "USB Serial Device" in desc:
|
|
48
|
+
target_port = port
|
|
49
|
+
break
|
|
50
|
+
|
|
51
|
+
serial = Serial(target_port, 9600, timeout=self._poll_rate)
|
|
52
|
+
while not self._kill_serial_event.is_set():
|
|
53
|
+
if serial.in_waiting > 0:
|
|
54
|
+
serial.readline()
|
|
55
|
+
# Cause a break
|
|
56
|
+
dprint("[EMERGENCY STOP]\t\t Stopping all manipulators")
|
|
57
|
+
self._server.platform.stop()
|
|
58
|
+
serial.reset_input_buffer()
|
|
59
|
+
sleep(self._poll_rate)
|
|
60
|
+
print("Close poll")
|
|
61
|
+
serial.close()
|
|
62
|
+
|
|
63
|
+
def _close_serial(self, _, __) -> None:
|
|
64
|
+
"""Close the serial connection"""
|
|
65
|
+
print("[INFO]\t\t Closing serial")
|
|
66
|
+
self._kill_serial_event.set()
|
|
67
|
+
self._poll_serial_thread.join()
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from json import dumps, load
|
|
2
|
+
from os import makedirs
|
|
3
|
+
from os.path import exists
|
|
4
|
+
from socket import gethostbyname, gethostname
|
|
5
|
+
from tkinter import CENTER, RIGHT, BooleanVar, E, IntVar, StringVar, Tk, ttk
|
|
6
|
+
|
|
7
|
+
from platformdirs import user_config_dir
|
|
8
|
+
|
|
9
|
+
import ephys_link.common as com
|
|
10
|
+
from ephys_link.__about__ import __version__ as version
|
|
11
|
+
from ephys_link.emergency_stop import EmergencyStop
|
|
12
|
+
from ephys_link.server import Server
|
|
13
|
+
|
|
14
|
+
SETTINGS_DIR = f"{user_config_dir()}\\VBL\\Ephys Link"
|
|
15
|
+
SETTINGS_FILENAME = "settings.json"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GUI:
|
|
19
|
+
"""GUI definition for Ephys Link"""
|
|
20
|
+
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
"""Setup and construction of the Tk GUI"""
|
|
23
|
+
|
|
24
|
+
self._root = Tk()
|
|
25
|
+
|
|
26
|
+
# Create default settings dictionary
|
|
27
|
+
settings = {"type": "sensapex", "debug": False, "port": 8081, "pathfinder_port": 8080, "serial": "no-e-stop"}
|
|
28
|
+
|
|
29
|
+
# Read settings.
|
|
30
|
+
if exists(f"{SETTINGS_DIR}\\{SETTINGS_FILENAME}"):
|
|
31
|
+
with open(f"{SETTINGS_DIR}\\{SETTINGS_FILENAME}") as settings_file:
|
|
32
|
+
settings = load(settings_file)
|
|
33
|
+
|
|
34
|
+
self._type = StringVar(value=settings["type"])
|
|
35
|
+
self._debug = BooleanVar(value=settings["debug"])
|
|
36
|
+
self._port = IntVar(value=settings["port"])
|
|
37
|
+
self._pathfinder_port = IntVar(value=settings["pathfinder_port"])
|
|
38
|
+
self._serial = StringVar(value=settings["serial"])
|
|
39
|
+
|
|
40
|
+
def launch(self) -> None:
|
|
41
|
+
"""Build and launch GUI"""
|
|
42
|
+
|
|
43
|
+
# Build and run GUI.
|
|
44
|
+
self._build_gui()
|
|
45
|
+
self._root.mainloop()
|
|
46
|
+
|
|
47
|
+
def _build_gui(self):
|
|
48
|
+
"""Build GUI"""
|
|
49
|
+
|
|
50
|
+
self._root.title(f"Ephys Link v{version}")
|
|
51
|
+
|
|
52
|
+
mainframe = ttk.Frame(self._root, padding=3)
|
|
53
|
+
mainframe.grid(column=0, row=0, sticky="news")
|
|
54
|
+
self._root.columnconfigure(0, weight=1)
|
|
55
|
+
self._root.rowconfigure(0, weight=1)
|
|
56
|
+
mainframe.columnconfigure(0, weight=1)
|
|
57
|
+
mainframe.rowconfigure(0, weight=1)
|
|
58
|
+
|
|
59
|
+
# Server serving settings.
|
|
60
|
+
|
|
61
|
+
server_serving_settings = ttk.LabelFrame(mainframe, text="Serving Settings", padding=3)
|
|
62
|
+
server_serving_settings.grid(column=0, row=0, sticky="news")
|
|
63
|
+
|
|
64
|
+
# IP.
|
|
65
|
+
ttk.Label(server_serving_settings, text="IP:", anchor=E, justify=RIGHT).grid(column=0, row=0, sticky="we")
|
|
66
|
+
ttk.Label(server_serving_settings, text=gethostbyname(gethostname())).grid(column=1, row=0, sticky="we")
|
|
67
|
+
|
|
68
|
+
# Port.
|
|
69
|
+
ttk.Label(server_serving_settings, text="Port:", anchor=E, justify=RIGHT).grid(column=0, row=1, sticky="we")
|
|
70
|
+
ttk.Entry(server_serving_settings, textvariable=self._port, width=5, justify=CENTER).grid(
|
|
71
|
+
column=1, row=1, sticky="we"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# ---
|
|
75
|
+
|
|
76
|
+
# Platform type.
|
|
77
|
+
platform_type_settings = ttk.LabelFrame(mainframe, text="Platform Type", padding=3)
|
|
78
|
+
platform_type_settings.grid(column=0, row=1, sticky="news")
|
|
79
|
+
|
|
80
|
+
ttk.Radiobutton(
|
|
81
|
+
platform_type_settings,
|
|
82
|
+
text="Sensapex uMp-4",
|
|
83
|
+
variable=self._type,
|
|
84
|
+
value="sensapex",
|
|
85
|
+
).grid(column=0, row=0, sticky="we")
|
|
86
|
+
ttk.Radiobutton(
|
|
87
|
+
platform_type_settings,
|
|
88
|
+
text="Sensapex uMp-3",
|
|
89
|
+
variable=self._type,
|
|
90
|
+
value="ump3",
|
|
91
|
+
).grid(column=0, row=1, sticky="we")
|
|
92
|
+
ttk.Radiobutton(
|
|
93
|
+
platform_type_settings,
|
|
94
|
+
text="Pathfinder MPM Control v2.8.8+",
|
|
95
|
+
variable=self._type,
|
|
96
|
+
value="new_scale_pathfinder",
|
|
97
|
+
).grid(column=0, row=2, sticky="we")
|
|
98
|
+
ttk.Radiobutton(
|
|
99
|
+
platform_type_settings,
|
|
100
|
+
text="New Scale M3-USB-3:1-EP",
|
|
101
|
+
variable=self._type,
|
|
102
|
+
value="new_scale",
|
|
103
|
+
).grid(column=0, row=3, sticky="we")
|
|
104
|
+
|
|
105
|
+
# ---
|
|
106
|
+
|
|
107
|
+
# New Scale Settings.
|
|
108
|
+
new_scale_settings = ttk.LabelFrame(mainframe, text="Pathfinder Settings", padding=3)
|
|
109
|
+
new_scale_settings.grid(column=0, row=2, sticky="news")
|
|
110
|
+
|
|
111
|
+
# Port
|
|
112
|
+
ttk.Label(new_scale_settings, text="HTTP Server Port:", anchor=E, justify=RIGHT).grid(
|
|
113
|
+
column=0, row=1, sticky="we"
|
|
114
|
+
)
|
|
115
|
+
ttk.Entry(new_scale_settings, textvariable=self._pathfinder_port, width=5, justify=CENTER).grid(
|
|
116
|
+
column=1, row=1, sticky="we"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# ---
|
|
120
|
+
|
|
121
|
+
# Emergency Stop serial port.
|
|
122
|
+
e_stop_settings = ttk.LabelFrame(mainframe, text="Emergency Stop Settings", padding=3)
|
|
123
|
+
e_stop_settings.grid(column=0, row=3, sticky="news")
|
|
124
|
+
|
|
125
|
+
# Serial Port
|
|
126
|
+
ttk.Label(e_stop_settings, text="Serial Port:", anchor=E, justify=RIGHT).grid(column=0, row=1, sticky="we")
|
|
127
|
+
ttk.Entry(e_stop_settings, textvariable=self._serial, justify=CENTER).grid(column=1, row=1, sticky="we")
|
|
128
|
+
|
|
129
|
+
# Server launch button.
|
|
130
|
+
ttk.Button(
|
|
131
|
+
mainframe,
|
|
132
|
+
text="Launch Server",
|
|
133
|
+
command=self._launch_server,
|
|
134
|
+
).grid(column=0, row=4, columnspan=2, sticky="we")
|
|
135
|
+
|
|
136
|
+
def _launch_server(self) -> None:
|
|
137
|
+
"""Launch server based on GUI settings"""
|
|
138
|
+
|
|
139
|
+
# Close GUI.
|
|
140
|
+
self._root.destroy()
|
|
141
|
+
|
|
142
|
+
# Save settings.
|
|
143
|
+
settings = {
|
|
144
|
+
"type": self._type.get(),
|
|
145
|
+
"debug": self._debug.get(),
|
|
146
|
+
"port": self._port.get(),
|
|
147
|
+
"pathfinder_port": self._pathfinder_port.get(),
|
|
148
|
+
"serial": self._serial.get(),
|
|
149
|
+
}
|
|
150
|
+
makedirs(SETTINGS_DIR, exist_ok=True)
|
|
151
|
+
with open(f"{SETTINGS_DIR}\\{SETTINGS_FILENAME}", "w+") as f:
|
|
152
|
+
f.write(dumps(settings))
|
|
153
|
+
|
|
154
|
+
# Launch server.
|
|
155
|
+
server = Server()
|
|
156
|
+
|
|
157
|
+
com.DEBUG = self._debug.get()
|
|
158
|
+
|
|
159
|
+
if self._serial.get() != "no-e-stop":
|
|
160
|
+
e_stop = EmergencyStop(server, self._serial.get())
|
|
161
|
+
e_stop.watch()
|
|
162
|
+
|
|
163
|
+
server.launch(self._type.get(), self._port.get(), self._pathfinder_port.get())
|
|
@@ -9,8 +9,11 @@ every event, the server does the following:
|
|
|
9
9
|
4. Relay the response from :mod:`ephys_link.sensapex_handler` to the callback function
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
12
14
|
import json
|
|
13
15
|
import sys
|
|
16
|
+
from signal import SIGINT, SIGTERM, signal
|
|
14
17
|
from typing import TYPE_CHECKING, Any
|
|
15
18
|
|
|
16
19
|
import socketio
|
|
@@ -40,10 +43,14 @@ class Server:
|
|
|
40
43
|
# Is the server running?
|
|
41
44
|
self.is_running = False
|
|
42
45
|
|
|
43
|
-
# Current platform handler (defaults to Sensapex)
|
|
46
|
+
# Current platform handler (defaults to Sensapex).
|
|
44
47
|
self.platform: PlatformHandler = SensapexHandler()
|
|
45
48
|
|
|
46
|
-
#
|
|
49
|
+
# Register server exit handlers.
|
|
50
|
+
signal(SIGTERM, self.close_server)
|
|
51
|
+
signal(SIGINT, self.close_server)
|
|
52
|
+
|
|
53
|
+
# Attach server to the web app.
|
|
47
54
|
self.sio.attach(self.app)
|
|
48
55
|
|
|
49
56
|
# Declare events
|
|
@@ -349,7 +356,7 @@ class Server:
|
|
|
349
356
|
print(f"[UNKNOWN EVENT]:\t {data}")
|
|
350
357
|
return "UNKNOWN_EVENT"
|
|
351
358
|
|
|
352
|
-
def
|
|
359
|
+
def launch(self, platform_type: str, server_port: int, pathfinder_port: int | None = None) -> None:
|
|
353
360
|
"""Launch the server.
|
|
354
361
|
|
|
355
362
|
:param platform_type: Parsed argument for platform type.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.1.1"
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import argparse
|
|
2
|
-
import signal
|
|
3
|
-
import time
|
|
4
|
-
from threading import Event, Thread
|
|
5
|
-
|
|
6
|
-
import serial
|
|
7
|
-
import serial.tools.list_ports as ports
|
|
8
|
-
|
|
9
|
-
from ephys_link import common as com
|
|
10
|
-
from ephys_link.__about__ import __version__ as version
|
|
11
|
-
from ephys_link.server import Server
|
|
12
|
-
|
|
13
|
-
# Setup Arduino serial port (emergency stop)
|
|
14
|
-
poll_rate = 0.05
|
|
15
|
-
kill_serial_event = Event()
|
|
16
|
-
poll_serial_thread: Thread
|
|
17
|
-
|
|
18
|
-
# Create Server
|
|
19
|
-
server = Server()
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def poll_serial(kill_event: Event, serial_port: str) -> None:
|
|
23
|
-
"""Continuously poll serial port for data
|
|
24
|
-
|
|
25
|
-
:param kill_event: Event to stop polling
|
|
26
|
-
:type kill_event: Event
|
|
27
|
-
:param serial_port: The serial port to poll
|
|
28
|
-
:type serial_port: str
|
|
29
|
-
:return: None
|
|
30
|
-
"""
|
|
31
|
-
target_port = serial_port
|
|
32
|
-
if serial_port is None:
|
|
33
|
-
# Search for serial ports
|
|
34
|
-
for port, desc, _ in ports.comports():
|
|
35
|
-
if "Arduino" in desc or "USB Serial Device" in desc:
|
|
36
|
-
target_port = port
|
|
37
|
-
break
|
|
38
|
-
elif serial_port == "no-e-stop":
|
|
39
|
-
# Stop polling if no-e-stop is specified
|
|
40
|
-
return None
|
|
41
|
-
|
|
42
|
-
ser = serial.Serial(target_port, 9600, timeout=poll_rate)
|
|
43
|
-
while not kill_event.is_set():
|
|
44
|
-
if ser.in_waiting > 0:
|
|
45
|
-
ser.readline()
|
|
46
|
-
# Cause a break
|
|
47
|
-
com.dprint("[EMERGENCY STOP]\t\t Stopping all manipulators")
|
|
48
|
-
server.platform.stop()
|
|
49
|
-
ser.reset_input_buffer()
|
|
50
|
-
time.sleep(poll_rate)
|
|
51
|
-
print("Close poll")
|
|
52
|
-
ser.close()
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def close_serial(_, __) -> None:
|
|
56
|
-
"""Close the serial connection"""
|
|
57
|
-
print("[INFO]\t\t Closing serial")
|
|
58
|
-
kill_serial_event.set()
|
|
59
|
-
poll_serial_thread.join()
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# Setup argument parser
|
|
63
|
-
parser = argparse.ArgumentParser(
|
|
64
|
-
description="Electrophysiology Manipulator Link: a websocket interface for"
|
|
65
|
-
" manipulators in electrophysiology experiments",
|
|
66
|
-
prog="python -m ephys-link",
|
|
67
|
-
)
|
|
68
|
-
# parser.add_argument("-g", "--gui", dest="gui", action="store_true", help="Launches GUI")
|
|
69
|
-
parser.add_argument(
|
|
70
|
-
"-t",
|
|
71
|
-
"--type",
|
|
72
|
-
type=str,
|
|
73
|
-
dest="type",
|
|
74
|
-
default="sensapex",
|
|
75
|
-
help='Manipulator type (i.e. "sensapex", "new_scale", or "new_scale_pathfinder").' ' Default: "sensapex"',
|
|
76
|
-
)
|
|
77
|
-
parser.add_argument("-d", "--debug", dest="debug", action="store_true", help="Enable debug mode")
|
|
78
|
-
parser.add_argument(
|
|
79
|
-
"-p",
|
|
80
|
-
"--port",
|
|
81
|
-
type=int,
|
|
82
|
-
default=8081,
|
|
83
|
-
dest="port",
|
|
84
|
-
help="Port to serve on. Default: 8081 (avoids conflict with other HTTP servers)",
|
|
85
|
-
)
|
|
86
|
-
parser.add_argument(
|
|
87
|
-
"--pathfinder_port",
|
|
88
|
-
type=int,
|
|
89
|
-
default=8080,
|
|
90
|
-
dest="pathfinder_port",
|
|
91
|
-
help="Port New Scale Pathfinder's server is on. Default: 8080",
|
|
92
|
-
)
|
|
93
|
-
parser.add_argument(
|
|
94
|
-
"-s",
|
|
95
|
-
"--serial",
|
|
96
|
-
type=str,
|
|
97
|
-
default="no-e-stop",
|
|
98
|
-
dest="serial",
|
|
99
|
-
nargs="?",
|
|
100
|
-
help="Emergency stop serial port (i.e. COM3). Default: disables emergency stop",
|
|
101
|
-
)
|
|
102
|
-
parser.add_argument(
|
|
103
|
-
"-v",
|
|
104
|
-
"--version",
|
|
105
|
-
action="version",
|
|
106
|
-
version=f"Electrophysiology Manipulator Link v{version}",
|
|
107
|
-
help="Print version and exit",
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def main() -> None:
|
|
112
|
-
"""Main function"""
|
|
113
|
-
|
|
114
|
-
# Parse arguments
|
|
115
|
-
args = parser.parse_args()
|
|
116
|
-
com.DEBUG = args.debug
|
|
117
|
-
|
|
118
|
-
# Setup serial port
|
|
119
|
-
if args.serial != "no-e-stop":
|
|
120
|
-
# Register serial exit
|
|
121
|
-
signal.signal(signal.SIGTERM, close_serial)
|
|
122
|
-
signal.signal(signal.SIGINT, close_serial)
|
|
123
|
-
|
|
124
|
-
# Start emergency stop system if serial is provided
|
|
125
|
-
global poll_serial_thread
|
|
126
|
-
poll_serial_thread = Thread(
|
|
127
|
-
target=poll_serial,
|
|
128
|
-
args=(
|
|
129
|
-
kill_serial_event,
|
|
130
|
-
args.serial,
|
|
131
|
-
),
|
|
132
|
-
daemon=True,
|
|
133
|
-
)
|
|
134
|
-
poll_serial_thread.start()
|
|
135
|
-
|
|
136
|
-
# Register server exit
|
|
137
|
-
signal.signal(signal.SIGTERM, server.close_server)
|
|
138
|
-
signal.signal(signal.SIGINT, server.close_server)
|
|
139
|
-
|
|
140
|
-
# Launch with parsed arguments on main thread
|
|
141
|
-
server.launch_server(args.type, args.port, args.pathfinder_port)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if __name__ == "__main__":
|
|
145
|
-
main()
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import socket
|
|
2
|
-
from argparse import Namespace
|
|
3
|
-
from threading import Event, Thread
|
|
4
|
-
from tkinter import CENTER, RIGHT, E, IntVar, StringVar, Tk, ttk
|
|
5
|
-
|
|
6
|
-
# GUI Variables
|
|
7
|
-
is_running = False
|
|
8
|
-
server_port: IntVar
|
|
9
|
-
platform_type: StringVar
|
|
10
|
-
new_scale_port: IntVar
|
|
11
|
-
e_stop_serial_port: StringVar
|
|
12
|
-
server_launch_button_text: StringVar
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class GUI:
|
|
16
|
-
"""GUI definition for Ephys Link
|
|
17
|
-
|
|
18
|
-
:param root: Root object of the Tk GUI
|
|
19
|
-
:type root: Tk
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
def __init__(
|
|
23
|
-
self,
|
|
24
|
-
root: Tk,
|
|
25
|
-
launch_func: callable,
|
|
26
|
-
manipulator_stop_func: callable,
|
|
27
|
-
poll_serial_func: callable,
|
|
28
|
-
parsed_args: Namespace,
|
|
29
|
-
) -> None:
|
|
30
|
-
"""Setup and construction of the Tk GUI"""
|
|
31
|
-
|
|
32
|
-
# Fields
|
|
33
|
-
self._root = root
|
|
34
|
-
self._launch_func = launch_func
|
|
35
|
-
self._manipulator_stop_func = manipulator_stop_func
|
|
36
|
-
self._poll_serial_func = poll_serial_func
|
|
37
|
-
self._kill_serial_event = Event()
|
|
38
|
-
self._parsed_args = parsed_args
|
|
39
|
-
|
|
40
|
-
# Update GUI variables with defaults
|
|
41
|
-
global server_port, platform_type, new_scale_port, e_stop_serial_port
|
|
42
|
-
global server_launch_button_text
|
|
43
|
-
server_port = IntVar(value=self._parsed_args.port)
|
|
44
|
-
platform_type = StringVar(value=self._parsed_args.type)
|
|
45
|
-
new_scale_port = IntVar(value=self._parsed_args.new_scale_port)
|
|
46
|
-
e_stop_serial_port = StringVar(value=self._parsed_args.serial)
|
|
47
|
-
server_launch_button_text = StringVar(value="Start Server")
|
|
48
|
-
|
|
49
|
-
# Build GUI
|
|
50
|
-
self.build_gui()
|
|
51
|
-
|
|
52
|
-
def build_gui(self):
|
|
53
|
-
"""Build GUI"""
|
|
54
|
-
|
|
55
|
-
self._root.title("Ephys Link")
|
|
56
|
-
|
|
57
|
-
mainframe = ttk.Frame(self._root, padding=3)
|
|
58
|
-
mainframe.grid(column=0, row=0, sticky="news")
|
|
59
|
-
self._root.columnconfigure(0, weight=1)
|
|
60
|
-
self._root.rowconfigure(0, weight=1)
|
|
61
|
-
mainframe.columnconfigure(0, weight=1)
|
|
62
|
-
mainframe.rowconfigure(0, weight=1)
|
|
63
|
-
|
|
64
|
-
# Server serving settings
|
|
65
|
-
|
|
66
|
-
server_serving_settings = ttk.LabelFrame(mainframe, text="Serving settings", padding=3)
|
|
67
|
-
server_serving_settings.grid(column=0, row=0, sticky="news")
|
|
68
|
-
|
|
69
|
-
# IP
|
|
70
|
-
ttk.Label(server_serving_settings, text="IP:", anchor=E, justify=RIGHT).grid(column=0, row=0, sticky="we")
|
|
71
|
-
ttk.Label(server_serving_settings, text=socket.gethostbyname(socket.gethostname())).grid(
|
|
72
|
-
column=1, row=0, sticky="we"
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
# Port
|
|
76
|
-
ttk.Label(server_serving_settings, text="Port:", anchor=E, justify=RIGHT).grid(column=0, row=1, sticky="we")
|
|
77
|
-
ttk.Entry(server_serving_settings, textvariable=server_port, width=5, justify=CENTER).grid(
|
|
78
|
-
column=1, row=1, sticky="we"
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
# ---
|
|
82
|
-
|
|
83
|
-
# Platform type
|
|
84
|
-
platform_type_settings = ttk.LabelFrame(mainframe, text="Platform Type", padding=3)
|
|
85
|
-
platform_type_settings.grid(column=0, row=1, sticky="news")
|
|
86
|
-
|
|
87
|
-
ttk.Radiobutton(
|
|
88
|
-
platform_type_settings,
|
|
89
|
-
text="Sensapex uMp",
|
|
90
|
-
variable=platform_type,
|
|
91
|
-
value="sensapex",
|
|
92
|
-
).grid(column=0, row=0, sticky="we")
|
|
93
|
-
ttk.Radiobutton(
|
|
94
|
-
platform_type_settings,
|
|
95
|
-
text="New Scale",
|
|
96
|
-
variable=platform_type,
|
|
97
|
-
value="new_scale",
|
|
98
|
-
).grid(column=0, row=1, sticky="we")
|
|
99
|
-
|
|
100
|
-
# ---
|
|
101
|
-
|
|
102
|
-
# New Scale Settings
|
|
103
|
-
new_scale_settings = ttk.LabelFrame(mainframe, text="New Scale settings", padding=3)
|
|
104
|
-
new_scale_settings.grid(column=0, row=2, sticky="news")
|
|
105
|
-
|
|
106
|
-
# Port
|
|
107
|
-
ttk.Label(new_scale_settings, text="HTTP Server Port:", anchor=E, justify=RIGHT).grid(
|
|
108
|
-
column=0, row=1, sticky="we"
|
|
109
|
-
)
|
|
110
|
-
ttk.Entry(new_scale_settings, textvariable=new_scale_port, width=5, justify=CENTER).grid(
|
|
111
|
-
column=1, row=1, sticky="we"
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
# ---
|
|
115
|
-
|
|
116
|
-
# Emergency Stop serial port
|
|
117
|
-
e_stop_settings = ttk.LabelFrame(mainframe, text="Emergency Stop Settings", padding=3)
|
|
118
|
-
e_stop_settings.grid(column=0, row=3, sticky="news")
|
|
119
|
-
|
|
120
|
-
# Serial Port
|
|
121
|
-
ttk.Label(e_stop_settings, text="Serial Port:", anchor=E, justify=RIGHT).grid(column=0, row=1, sticky="we")
|
|
122
|
-
ttk.Entry(e_stop_settings, textvariable=e_stop_serial_port, justify=CENTER).grid(column=1, row=1, sticky="we")
|
|
123
|
-
|
|
124
|
-
# Server start/stop button
|
|
125
|
-
ttk.Button(
|
|
126
|
-
mainframe,
|
|
127
|
-
textvariable=server_launch_button_text,
|
|
128
|
-
command=lambda: self.start_stop_server(not is_running),
|
|
129
|
-
).grid(column=0, row=4, columnspan=2, sticky="we")
|
|
130
|
-
|
|
131
|
-
def start_stop_server(self, start: bool) -> None:
|
|
132
|
-
"""Start/stop server and update button text
|
|
133
|
-
|
|
134
|
-
:param start: Whether to start or stop the server
|
|
135
|
-
:type start: bool
|
|
136
|
-
:return None
|
|
137
|
-
"""
|
|
138
|
-
global is_running
|
|
139
|
-
is_running = not is_running
|
|
140
|
-
if start:
|
|
141
|
-
# Launch serial
|
|
142
|
-
Thread(
|
|
143
|
-
target=self._poll_serial_func,
|
|
144
|
-
args=(
|
|
145
|
-
self._kill_serial_event,
|
|
146
|
-
e_stop_serial_port.get(),
|
|
147
|
-
),
|
|
148
|
-
daemon=True,
|
|
149
|
-
).start()
|
|
150
|
-
# Launch server
|
|
151
|
-
Thread(
|
|
152
|
-
target=self._launch_func,
|
|
153
|
-
args=(platform_type.get(), server_port.get(), new_scale_port.get()),
|
|
154
|
-
daemon=True,
|
|
155
|
-
).start()
|
|
156
|
-
|
|
157
|
-
# Update UI
|
|
158
|
-
server_launch_button_text.set("Close Server")
|
|
159
|
-
else:
|
|
160
|
-
# Stop serial
|
|
161
|
-
self._kill_serial_event.set()
|
|
162
|
-
|
|
163
|
-
# Stop manipulators
|
|
164
|
-
self._manipulator_stop_func(0)
|
|
165
|
-
|
|
166
|
-
# Close
|
|
167
|
-
self._root.destroy()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/new_scale_pathfinder_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|