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.
Files changed (32) hide show
  1. {ephys_link-1.1.1 → ephys_link-1.2.0}/.gitignore +0 -1
  2. {ephys_link-1.1.1 → ephys_link-1.2.0}/PKG-INFO +62 -45
  3. {ephys_link-1.1.1 → ephys_link-1.2.0}/README.md +60 -44
  4. ephys_link-1.2.0/assets/icon.ico +0 -0
  5. ephys_link-1.2.0/ephys_link.spec +39 -0
  6. {ephys_link-1.1.1 → ephys_link-1.2.0}/pyproject.toml +9 -0
  7. ephys_link-1.2.0/src/ephys_link/__about__.py +1 -0
  8. ephys_link-1.2.0/src/ephys_link/__main__.py +88 -0
  9. ephys_link-1.2.0/src/ephys_link/emergency_stop.py +67 -0
  10. ephys_link-1.2.0/src/ephys_link/gui.py +163 -0
  11. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/server.py +10 -3
  12. ephys_link-1.1.1/src/ephys_link/__about__.py +0 -1
  13. ephys_link-1.1.1/src/ephys_link/__main__.py +0 -145
  14. ephys_link-1.1.1/src/ephys_link/gui.py +0 -167
  15. {ephys_link-1.1.1 → ephys_link-1.2.0}/LICENSE +0 -0
  16. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/__init__.py +0 -0
  17. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/common.py +0 -0
  18. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platform_handler.py +0 -0
  19. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platform_manipulator.py +0 -0
  20. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/__init__.py +0 -0
  21. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/new_scale_handler.py +0 -0
  22. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/new_scale_manipulator.py +0 -0
  23. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/new_scale_pathfinder_handler.py +0 -0
  24. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/sensapex_handler.py +0 -0
  25. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/sensapex_manipulator.py +0 -0
  26. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/ump3_handler.py +0 -0
  27. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/platforms/ump3_manipulator.py +0 -0
  28. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/resources/CP210xManufacturing.dll +0 -0
  29. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/resources/NstMotorCtrl.dll +0 -0
  30. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/resources/SiUSBXp.dll +0 -0
  31. {ephys_link-1.1.1 → ephys_link-1.2.0}/src/ephys_link/resources/libum.dll +0 -0
  32. {ephys_link-1.1.1 → ephys_link-1.2.0}/tests/__init__.py +0 -0
@@ -32,7 +32,6 @@ MANIFEST
32
32
  # Usually these files are written by a python script from a template
33
33
  # before PyInstaller builds the exe, so as to inject date/other infos into it.
34
34
  *.manifest
35
- *.spec
36
35
 
37
36
  # Installer logs
38
37
  pip-log.txt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ephys-link
3
- Version: 1.1.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
  [![PyPI version](https://badge.fury.io/py/ephys-link.svg)](https://badge.fury.io/py/ephys-link)
41
+ [![Build](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/build.yml/badge.svg)](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/build.yml)
40
42
  [![CodeQL](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/codeql-analysis.yml)
41
43
  [![Dependency Review](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/dependency-review.yml/badge.svg)](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/dependency-review.yml)
42
44
  [![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](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</li> <li>M3-USB-3:1-EP</li> </ul> |
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. [Python 3.8, < 3.13](https://www.python.org/downloads/release/python-3116/)
73
- 1. Python 3.12+ requires the latest version
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
- 4. To use the emergency stop feature, ensure an Arduino with
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
- <div style="padding: 15px; border: 1px solid transparent; border-color: transparent; margin-bottom: 20px; border-radius: 4px; color: #31708f; background-color: #d9edf7; border-color: #bce8f1;">
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
- ## Install for use
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
- Run the following command to install the server:
96
+ The configuration window will close and the server will launch. Your configurations will be saved for future use.
101
97
 
102
- ```bash
103
- pip install ephys-link
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
- Update the server like any other Python package:
101
+ - Server: `localhost`
102
+ - Port: `8081`
107
103
 
108
- ```bash
109
- pip install --upgrade ephys-link
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 development
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. In a terminal, navigate to the repository's root directory and run
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
- # Usage
121
+ ## Install as a Python package
125
122
 
126
- Run the following commands in a terminal to start the server for the desired manipulator platform:
123
+ ```bash
124
+ pip install ephys-link
125
+ ```
127
126
 
128
- | Manipulator Platform | Command |
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
- There are a couple additional aliases for the Ephys Link executable: `ephys_link` and `el`.
129
+ ```python
130
+ from ephys_link.server import Server
136
131
 
137
- By default, the server will broadcast with its local IP address on port 8081.
138
- **Copy this information into Pinpoint to connect**.
132
+ server = Server()
133
+ server.launch("sensapex", 8081)
134
+ ```
139
135
 
140
- For example, if the server is running on the same computer that Pinpoint is, use
136
+ # CLI Usage
141
137
 
142
- - Server: `localhost`
143
- - Port: `8081`
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
  [![PyPI version](https://badge.fury.io/py/ephys-link.svg)](https://badge.fury.io/py/ephys-link)
4
+ [![Build](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/build.yml/badge.svg)](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/build.yml)
4
5
  [![CodeQL](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/codeql-analysis.yml)
5
6
  [![Dependency Review](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/dependency-review.yml/badge.svg)](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/dependency-review.yml)
6
7
  [![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](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</li> <li>M3-USB-3:1-EP</li> </ul> |
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. [Python 3.8, < 3.13](https://www.python.org/downloads/release/python-3116/)
37
- 1. Python 3.12+ requires the latest version
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
- 4. To use the emergency stop feature, ensure an Arduino with
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
- <div style="padding: 15px; border: 1px solid transparent; border-color: transparent; margin-bottom: 20px; border-radius: 4px; color: #31708f; background-color: #d9edf7; border-color: #bce8f1;">
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
- ## Install for use
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
- Run the following command to install the server:
59
+ The configuration window will close and the server will launch. Your configurations will be saved for future use.
65
60
 
66
- ```bash
67
- pip install ephys-link
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
- Update the server like any other Python package:
64
+ - Server: `localhost`
65
+ - Port: `8081`
71
66
 
72
- ```bash
73
- pip install --upgrade ephys-link
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 development
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. In a terminal, navigate to the repository's root directory and run
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
- # Usage
84
+ ## Install as a Python package
89
85
 
90
- Run the following commands in a terminal to start the server for the desired manipulator platform:
86
+ ```bash
87
+ pip install ephys-link
88
+ ```
91
89
 
92
- | Manipulator Platform | Command |
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
- There are a couple additional aliases for the Ephys Link executable: `ephys_link` and `el`.
92
+ ```python
93
+ from ephys_link.server import Server
100
94
 
101
- By default, the server will broadcast with its local IP address on port 8081.
102
- **Copy this information into Pinpoint to connect**.
95
+ server = Server()
96
+ server.launch("sensapex", 8081)
97
+ ```
103
98
 
104
- For example, if the server is running on the same computer that Pinpoint is, use
99
+ # CLI Usage
105
100
 
106
- - Server: `localhost`
107
- - Port: `8081`
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
- # Attach server to the web app
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 launch_server(self, platform_type: str, server_port: int, pathfinder_port: int) -> None:
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