ephys-link 1.3.3__tar.gz → 2.0.0b1__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 (48) hide show
  1. {ephys_link-1.3.3 → ephys_link-2.0.0b1}/PKG-INFO +6 -4
  2. {ephys_link-1.3.3 → ephys_link-2.0.0b1}/ephys_link.spec +5 -0
  3. {ephys_link-1.3.3 → ephys_link-2.0.0b1}/pyproject.toml +15 -9
  4. ephys_link-2.0.0b1/scripts/move_tester.py +15 -0
  5. ephys_link-2.0.0b1/scripts/server_tester.py +23 -0
  6. ephys_link-2.0.0b1/src/ephys_link/__about__.py +1 -0
  7. ephys_link-2.0.0b1/src/ephys_link/__main__.py +43 -0
  8. ephys_link-2.0.0b1/src/ephys_link/back_end/__init__.py +0 -0
  9. ephys_link-2.0.0b1/src/ephys_link/back_end/platform_handler.py +298 -0
  10. ephys_link-2.0.0b1/src/ephys_link/back_end/server.py +200 -0
  11. ephys_link-2.0.0b1/src/ephys_link/bindings/__init__.py +0 -0
  12. ephys_link-2.0.0b1/src/ephys_link/bindings/fake_bindings.py +54 -0
  13. ephys_link-2.0.0b1/src/ephys_link/bindings/ump_4_bindings.py +127 -0
  14. ephys_link-2.0.0b1/src/ephys_link/front_end/__init__.py +0 -0
  15. ephys_link-2.0.0b1/src/ephys_link/front_end/cli.py +98 -0
  16. {ephys_link-1.3.3/src/ephys_link → ephys_link-2.0.0b1/src/ephys_link/front_end}/gui.py +93 -95
  17. ephys_link-2.0.0b1/src/ephys_link/util/__init__.py +0 -0
  18. ephys_link-2.0.0b1/src/ephys_link/util/base_bindings.py +133 -0
  19. ephys_link-2.0.0b1/src/ephys_link/util/common.py +121 -0
  20. ephys_link-2.0.0b1/src/ephys_link/util/console.py +112 -0
  21. ephys_link-1.3.3/scripts/move_tester.py +0 -21
  22. ephys_link-1.3.3/scripts/proxy_dev.py +0 -24
  23. ephys_link-1.3.3/src/ephys_link/__about__.py +0 -1
  24. ephys_link-1.3.3/src/ephys_link/__main__.py +0 -105
  25. ephys_link-1.3.3/src/ephys_link/common.py +0 -49
  26. ephys_link-1.3.3/src/ephys_link/emergency_stop.py +0 -67
  27. ephys_link-1.3.3/src/ephys_link/platform_handler.py +0 -465
  28. ephys_link-1.3.3/src/ephys_link/platform_manipulator.py +0 -35
  29. ephys_link-1.3.3/src/ephys_link/platforms/__init__.py +0 -5
  30. ephys_link-1.3.3/src/ephys_link/platforms/new_scale_handler.py +0 -141
  31. ephys_link-1.3.3/src/ephys_link/platforms/new_scale_manipulator.py +0 -312
  32. ephys_link-1.3.3/src/ephys_link/platforms/new_scale_pathfinder_handler.py +0 -235
  33. ephys_link-1.3.3/src/ephys_link/platforms/sensapex_handler.py +0 -151
  34. ephys_link-1.3.3/src/ephys_link/platforms/sensapex_manipulator.py +0 -227
  35. ephys_link-1.3.3/src/ephys_link/platforms/ump3_handler.py +0 -57
  36. ephys_link-1.3.3/src/ephys_link/platforms/ump3_manipulator.py +0 -147
  37. ephys_link-1.3.3/src/ephys_link/server.py +0 -508
  38. {ephys_link-1.3.3 → ephys_link-2.0.0b1}/.gitignore +0 -0
  39. {ephys_link-1.3.3 → ephys_link-2.0.0b1}/LICENSE +0 -0
  40. {ephys_link-1.3.3 → ephys_link-2.0.0b1}/README.md +0 -0
  41. {ephys_link-1.3.3 → ephys_link-2.0.0b1}/assets/icon.ico +0 -0
  42. {ephys_link-1.3.3 → ephys_link-2.0.0b1}/scripts/__init__.py +0 -0
  43. {ephys_link-1.3.3 → ephys_link-2.0.0b1}/src/ephys_link/__init__.py +0 -0
  44. {ephys_link-1.3.3 → ephys_link-2.0.0b1}/src/ephys_link/resources/CP210xManufacturing.dll +0 -0
  45. {ephys_link-1.3.3 → ephys_link-2.0.0b1}/src/ephys_link/resources/NstMotorCtrl.dll +0 -0
  46. {ephys_link-1.3.3 → ephys_link-2.0.0b1}/src/ephys_link/resources/SiUSBXp.dll +0 -0
  47. {ephys_link-1.3.3 → ephys_link-2.0.0b1}/src/ephys_link/resources/libum.dll +0 -0
  48. {ephys_link-1.3.3 → ephys_link-2.0.0b1}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ephys-link
3
- Version: 1.3.3
3
+ Version: 2.0.0b1
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,12 +27,14 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
27
27
  Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
28
28
  Requires-Python: <3.13,>=3.10
29
29
  Requires-Dist: aiohttp==3.9.5
30
+ Requires-Dist: colorama==0.4.6
30
31
  Requires-Dist: platformdirs==4.2.2
31
32
  Requires-Dist: pyserial==3.5
32
- Requires-Dist: python-socketio[asyncio-client]==5.11.2
33
+ Requires-Dist: python-socketio[asyncio-client]==5.11.3
33
34
  Requires-Dist: pythonnet==3.0.3
34
- Requires-Dist: sensapex==1.400.0
35
- Requires-Dist: vbl-aquarium==0.0.15
35
+ Requires-Dist: requests==2.32.3
36
+ Requires-Dist: sensapex==1.400.1
37
+ Requires-Dist: vbl-aquarium==0.0.19
36
38
  Description-Content-Type: text/markdown
37
39
 
38
40
  # Electrophysiology Manipulator Link
@@ -10,6 +10,7 @@ options = parser.parse_args()
10
10
 
11
11
  FILE_NAME = f"EphysLink-v{version}"
12
12
 
13
+ # noinspection PyUnresolvedReferences
13
14
  a = Analysis(
14
15
  ['src\\ephys_link\\__main__.py'],
15
16
  pathex=[],
@@ -23,9 +24,11 @@ a = Analysis(
23
24
  noarchive=False,
24
25
  optimize=1,
25
26
  )
27
+ # noinspection PyUnresolvedReferences
26
28
  pyz = PYZ(a.pure)
27
29
 
28
30
  if options.dir:
31
+ # noinspection PyUnresolvedReferences
29
32
  exe = EXE(
30
33
  pyz,
31
34
  a.scripts,
@@ -45,8 +48,10 @@ if options.dir:
45
48
  entitlements_file=None,
46
49
  icon='assets\\icon.ico',
47
50
  )
51
+ # noinspection PyUnresolvedReferences
48
52
  coll = COLLECT(exe, a.binaries, a.datas, strip=False, upx=True, upx_exclude=[], name=FILE_NAME)
49
53
  else:
54
+ # noinspection PyUnresolvedReferences
50
55
  exe = EXE(
51
56
  pyz,
52
57
  a.scripts,
@@ -31,12 +31,14 @@ classifiers = [
31
31
  ]
32
32
  dependencies = [
33
33
  "aiohttp==3.9.5",
34
+ "colorama==0.4.6",
34
35
  "platformdirs==4.2.2",
35
36
  "pyserial==3.5",
36
- "python-socketio[asyncio_client]==5.11.2",
37
+ "python-socketio[asyncio_client]==5.11.3",
37
38
  "pythonnet==3.0.3",
38
- "sensapex==1.400.0",
39
- "vbl-aquarium==0.0.15"
39
+ "requests==2.32.3",
40
+ "sensapex==1.400.1",
41
+ "vbl-aquarium==0.0.19"
40
42
  ]
41
43
 
42
44
  [project.urls]
@@ -59,7 +61,6 @@ exclude = ["/.github", "/.idea"]
59
61
  python = "3.12"
60
62
  dependencies = [
61
63
  "coverage[toml]>=6.5",
62
- "mypy>=1.0.0",
63
64
  "pytest",
64
65
  ]
65
66
  [tool.hatch.envs.default.scripts]
@@ -73,7 +74,6 @@ cov = [
73
74
  "test-cov",
74
75
  "cov-report",
75
76
  ]
76
- types = "mypy --strict --install-types --non-interactive {args:src/ephys_link tests}"
77
77
 
78
78
  #[[tool.hatch.envs.all.matrix]]
79
79
  #python = ["3.8", "3.9", "3.10", "3.11", "3.12"]
@@ -88,6 +88,15 @@ build = "pyinstaller.exe ephys_link.spec -y -- -d && pyinstaller.exe ephys_link.
88
88
  build_onefile = "pyinstaller.exe ephys_link.spec -y"
89
89
  build_clean = "pyinstaller.exe ephys_link.spec -y --clean"
90
90
 
91
+ [tool.hatch.envs.types]
92
+ python = "3.12"
93
+ skip-install = true
94
+ dependencies = [
95
+ "mypy",
96
+ ]
97
+ [tool.hatch.envs.types.scripts]
98
+ check = "mypy --strict --install-types --non-interactive --ignore-missing-imports {args:src/ephys_link tests}"
99
+
91
100
  [tool.coverage.run]
92
101
  source_pkgs = ["ephys_link", "tests"]
93
102
  branch = true
@@ -105,7 +114,4 @@ exclude_lines = [
105
114
  "no cov",
106
115
  "if __name__ == .__main__.:",
107
116
  "if TYPE_CHECKING:",
108
- ]
109
-
110
- [tool.ruff.lint]
111
- extend-ignore = ["T201", "PLW0603", "BLE001", "FBT001", "ARG002", "S310"]
117
+ ]
@@ -0,0 +1,15 @@
1
+ from asyncio import run
2
+
3
+ from vbl_aquarium.models.ephys_link import SetPositionRequest
4
+ from vbl_aquarium.models.unity import Vector4
5
+
6
+ from ephys_link.back_end.platform_handler import PlatformHandler
7
+ from ephys_link.util.console import Console
8
+
9
+ c = Console(enable_debug=True)
10
+ p = PlatformHandler("ump-4", c)
11
+ target = Vector4()
12
+ # target = Vector4(x=10, y=10, z=10, w=10)
13
+
14
+ print(run(p.set_position(SetPositionRequest(manipulator_id="6", position=target, speed=5))).to_json_string())
15
+ print("Done!")
@@ -0,0 +1,23 @@
1
+ from time import sleep
2
+
3
+ from socketio import SimpleClient
4
+ from vbl_aquarium.models.ephys_link import SetDepthRequest, SetInsideBrainRequest
5
+ from vbl_aquarium.models.unity import Vector4
6
+
7
+ with SimpleClient() as sio:
8
+ sio.connect("http://localhost:3000")
9
+
10
+ print(sio.call("set_inside_brain", SetInsideBrainRequest(manipulator_id="6", inside=True).to_json_string()))
11
+
12
+ target = Vector4()
13
+ # target = Vector4(x=10, y=10, z=10, w=10)
14
+
15
+ sio.emit(
16
+ "set_depth",
17
+ SetDepthRequest(manipulator_id="6", depth=10, speed=3).to_json_string(),
18
+ )
19
+ sleep(1)
20
+ print(sio.call("stop"))
21
+ # while True:
22
+ # print(sio.call("get_position", "6"))
23
+ sio.disconnect()
@@ -0,0 +1 @@
1
+ __version__ = "2.0.0b1"
@@ -0,0 +1,43 @@
1
+ """Ephys Link entry point.
2
+
3
+ Responsible for gathering launch options, instantiating the appropriate interface, and starting the application.
4
+
5
+ Usage: call main() to start.
6
+ """
7
+
8
+ from sys import argv
9
+
10
+ from ephys_link.back_end.platform_handler import PlatformHandler
11
+ from ephys_link.back_end.server import Server
12
+ from ephys_link.front_end.cli import CLI
13
+ from ephys_link.front_end.gui import GUI
14
+ from ephys_link.util.console import Console
15
+
16
+
17
+ def main() -> None:
18
+ """Ephys Link entry point.
19
+
20
+ 1. Get options via CLI or GUI.
21
+ 2. Instantiate the Console and make it globally accessible.
22
+ 3. Instantiate the Platform Handler with the appropriate platform bindings.
23
+ 4. Instantiate the Emergency Stop service.
24
+ 5. Start the server.
25
+ """
26
+
27
+ # 1. Get options via CLI or GUI (if no CLI options are provided).
28
+ options = CLI().parse_args() if len(argv) > 1 else GUI().get_options()
29
+
30
+ # 2. Instantiate the Console and make it globally accessible.
31
+ console = Console(enable_debug=options.debug)
32
+
33
+ # 3. Instantiate the Platform Handler with the appropriate platform bindings.
34
+ platform_handler = PlatformHandler(options.type, console)
35
+
36
+ # 4. Instantiate the Emergency Stop service.
37
+
38
+ # 5. Start the server.
39
+ Server(options, platform_handler, console).launch()
40
+
41
+
42
+ if __name__ == "__main__":
43
+ main()
File without changes
@@ -0,0 +1,298 @@
1
+ # ruff: noqa: BLE001
2
+ """Manipulator platform handler.
3
+
4
+ Responsible for performing the various manipulator commands.
5
+ Instantiates the appropriate bindings based on the platform type and uses them to perform the commands.
6
+
7
+ Usage: Instantiate PlatformHandler with the platform type and call the desired command.
8
+ """
9
+
10
+ from uuid import uuid4
11
+
12
+ from vbl_aquarium.models.ephys_link import (
13
+ AngularResponse,
14
+ BooleanStateResponse,
15
+ GetManipulatorsResponse,
16
+ PositionalResponse,
17
+ SetDepthRequest,
18
+ SetDepthResponse,
19
+ SetInsideBrainRequest,
20
+ SetPositionRequest,
21
+ ShankCountResponse,
22
+ )
23
+ from vbl_aquarium.models.proxy import PinpointIdResponse
24
+ from vbl_aquarium.models.unity import Vector4
25
+
26
+ from ephys_link.__about__ import __version__
27
+ from ephys_link.bindings.fake_bindings import FakeBindings
28
+ from ephys_link.bindings.ump_4_bindings import Ump4Bindings
29
+ from ephys_link.util.base_bindings import BaseBindings
30
+ from ephys_link.util.common import vector4_to_array
31
+ from ephys_link.util.console import Console
32
+
33
+
34
+ class PlatformHandler:
35
+ """Handler for platform commands."""
36
+
37
+ def __init__(self, platform_type: str, console: Console) -> None:
38
+ """Initialize platform handler.
39
+
40
+ :param platform_type: Platform type to initialize bindings from.
41
+ :type platform_type: str
42
+ """
43
+
44
+ # Store the platform type.
45
+ self._platform_type = platform_type
46
+
47
+ # Store the console.
48
+ self._console = console
49
+
50
+ # Define bindings based on platform type.
51
+ self._bindings = self._match_platform_type(platform_type)
52
+
53
+ # Record which IDs are inside the brain.
54
+ self._inside_brain: set[str] = set()
55
+
56
+ # Generate a Pinpoint ID for proxy usage.
57
+ self._pinpoint_id = str(uuid4())[:8]
58
+
59
+ def _match_platform_type(self, platform_type: str) -> BaseBindings:
60
+ """Match the platform type to the appropriate bindings.
61
+
62
+ :param platform_type: Platform type.
63
+ :type platform_type: str
64
+ :returns: Bindings for the specified platform type.
65
+ :rtype: :class:`ephys_link.util.base_bindings.BaseBindings`
66
+ """
67
+ match platform_type:
68
+ case "ump-4":
69
+ return Ump4Bindings()
70
+ case "fake":
71
+ return FakeBindings()
72
+ case _:
73
+ error_message = f'Platform type "{platform_type}" not recognized.'
74
+ self._console.labeled_error_print("PLATFORM", error_message)
75
+ raise ValueError(error_message)
76
+
77
+ # Ephys Link metadata.
78
+
79
+ @staticmethod
80
+ def get_version() -> str:
81
+ """Get Ephys Link's version.
82
+
83
+ :returns: Ephys Link's version.
84
+ :rtype: str
85
+ """
86
+ return __version__
87
+
88
+ def get_pinpoint_id(self) -> PinpointIdResponse:
89
+ """Get the Pinpoint ID for proxy usage.
90
+
91
+ :returns: Pinpoint ID response.
92
+ :rtype: :class:`vbl_aquarium.models.ephys_link.PinpointIDResponse`
93
+ """
94
+ return PinpointIdResponse(pinpoint_id=self._pinpoint_id, is_requester=False)
95
+
96
+ def get_platform_type(self) -> str:
97
+ """Get the manipulator platform type connected to Ephys Link.
98
+
99
+ :returns: Platform type config identifier (see CLI options for examples).
100
+ :rtype: str
101
+ """
102
+ return self._platform_type
103
+
104
+ # Manipulator commands.
105
+
106
+ async def get_manipulators(self) -> GetManipulatorsResponse:
107
+ """Get a list of available manipulators on the current handler.
108
+
109
+ :returns: List of manipulator IDs, number of axes, dimensions of manipulators (mm), and an error message if any.
110
+ :rtype: :class:`vbl_aquarium.models.ephys_link.GetManipulatorsResponse`
111
+ """
112
+ try:
113
+ manipulators = await self._bindings.get_manipulators()
114
+ num_axes = await self._bindings.get_num_axes()
115
+ dimensions = self._bindings.get_dimensions()
116
+ except Exception as e:
117
+ self._console.exception_error_print("Get Manipulators", e)
118
+ return GetManipulatorsResponse(error=self._console.pretty_exception(e))
119
+ else:
120
+ return GetManipulatorsResponse(
121
+ manipulators=manipulators,
122
+ num_axes=num_axes,
123
+ dimensions=dimensions,
124
+ )
125
+
126
+ async def get_position(self, manipulator_id: str) -> PositionalResponse:
127
+ """Get the current translation position of a manipulator in unified coordinates (mm).
128
+
129
+ :param manipulator_id: Manipulator ID.
130
+ :type manipulator_id: str
131
+ :returns: Current position of the manipulator and an error message if any.
132
+ :rtype: :class:`vbl_aquarium.models.ephys_link.PositionalResponse`
133
+ """
134
+ try:
135
+ unified_position = self._bindings.platform_space_to_unified_space(
136
+ await self._bindings.get_position(manipulator_id)
137
+ )
138
+ except Exception as e:
139
+ self._console.exception_error_print("Get Position", e)
140
+ return PositionalResponse(error=str(e))
141
+ else:
142
+ return PositionalResponse(position=unified_position)
143
+
144
+ async def get_angles(self, manipulator_id: str) -> AngularResponse:
145
+ """Get the current rotation angles of a manipulator in Yaw, Pitch, Roll (degrees).
146
+
147
+ :param manipulator_id: Manipulator ID.
148
+ :type manipulator_id: str
149
+ :returns: Current angles of the manipulator and an error message if any.
150
+ :rtype: :class:`vbl_aquarium.models.ephys_link.AngularResponse`
151
+ """
152
+ try:
153
+ angles = await self._bindings.get_angles(manipulator_id)
154
+ except Exception as e:
155
+ self._console.exception_error_print("Get Angles", e)
156
+ return AngularResponse(error=self._console.pretty_exception(e))
157
+ else:
158
+ return AngularResponse(angles=angles)
159
+
160
+ async def get_shank_count(self, manipulator_id: str) -> ShankCountResponse:
161
+ """Get the number of shanks on a manipulator.
162
+
163
+ :param manipulator_id: Manipulator ID.
164
+ :type manipulator_id: str
165
+ :returns: Number of shanks on the manipulator and an error message if any.
166
+ :rtype: :class:`vbl_aquarium.models.ephys_link.ShankCountResponse`
167
+ """
168
+ try:
169
+ shank_count = await self._bindings.get_shank_count(manipulator_id)
170
+ except Exception as e:
171
+ self._console.exception_error_print("Get Shank Count", e)
172
+ return ShankCountResponse(error=self._console.pretty_exception(e))
173
+ else:
174
+ return ShankCountResponse(shank_count=shank_count)
175
+
176
+ async def set_position(self, request: SetPositionRequest) -> PositionalResponse:
177
+ """Move a manipulator to a specified translation position in unified coordinates (mm).
178
+
179
+ :param request: Request to move a manipulator to a specified position.
180
+ :type request: :class:`vbl_aquarium.models.ephys_link.GotoPositionRequest`
181
+ :returns: Final position of the manipulator and an error message if any.
182
+ :rtype: :class:`vbl_aquarium.models.ephys_link.Position`
183
+ """
184
+ try:
185
+ # Disallow setting manipulator position while inside the brain.
186
+ if request.manipulator_id in self._inside_brain:
187
+ error_message = 'Can not move manipulator while inside the brain. Set the depth ("set_depth") instead.'
188
+ self._console.error_print(error_message)
189
+ return PositionalResponse(error=error_message)
190
+
191
+ # Move to the new position.
192
+ final_platform_position = await self._bindings.set_position(
193
+ manipulator_id=request.manipulator_id,
194
+ position=self._bindings.unified_space_to_platform_space(request.position),
195
+ speed=request.speed,
196
+ )
197
+ final_unified_position = self._bindings.platform_space_to_unified_space(final_platform_position)
198
+
199
+ # Return error if movement did not reach target within tolerance.
200
+ for index, axis in enumerate(vector4_to_array(final_unified_position - request.position)):
201
+ # End once index is greater than the number of axes.
202
+ if index >= await self._bindings.get_num_axes():
203
+ break
204
+
205
+ # Check if the axis is within the movement tolerance.
206
+ if abs(axis) > await self._bindings.get_movement_tolerance():
207
+ error_message = (
208
+ f"Manipulator {request.manipulator_id} did not reach target"
209
+ f" position on axis {list(Vector4.model_fields.keys())[index]}."
210
+ f"Requested: {request.position}, got: {final_unified_position}."
211
+ )
212
+ self._console.error_print(error_message)
213
+ return PositionalResponse(error=error_message)
214
+ except Exception as e:
215
+ self._console.exception_error_print("Set Position", e)
216
+ return PositionalResponse(error=self._console.pretty_exception(e))
217
+ else:
218
+ return PositionalResponse(position=final_unified_position)
219
+
220
+ async def set_depth(self, request: SetDepthRequest) -> SetDepthResponse:
221
+ """Move a manipulator's depth translation stage to a specific value (mm).
222
+
223
+ :param request: Request to move a manipulator to a specified depth.
224
+ :type request: :class:`vbl_aquarium.models.ephys_link.DriveToDepthRequest`
225
+ :returns: Final depth of the manipulator and an error message if any.
226
+ :rtype: :class:`vbl_aquarium.models.ephys_link.DriveToDepthResponse`
227
+ """
228
+ try:
229
+ # Create a position based on the new depth.
230
+ current_platform_position = await self._bindings.get_position(request.manipulator_id)
231
+ current_unified_position = self._bindings.platform_space_to_unified_space(current_platform_position)
232
+ target_unified_position = current_unified_position.model_copy(update={"w": request.depth})
233
+ target_platform_position = self._bindings.unified_space_to_platform_space(target_unified_position)
234
+
235
+ # Move to the new depth.
236
+ final_platform_position = await self._bindings.set_position(
237
+ manipulator_id=request.manipulator_id,
238
+ position=target_platform_position,
239
+ speed=request.speed,
240
+ )
241
+ final_unified_position = self._bindings.platform_space_to_unified_space(final_platform_position)
242
+ except Exception as e:
243
+ self._console.exception_error_print("Set Depth", e)
244
+ return SetDepthResponse(error=self._console.pretty_exception(e))
245
+ else:
246
+ return SetDepthResponse(depth=final_unified_position.w)
247
+
248
+ async def set_inside_brain(self, request: SetInsideBrainRequest) -> BooleanStateResponse:
249
+ """Mark a manipulator as inside the brain or not.
250
+
251
+ This should restrict the manipulator's movement to just the depth axis.
252
+
253
+ :param request: Request to set a manipulator's inside brain state.
254
+ :type request: :class:`vbl_aquarium.models.ephys_link.InsideBrainRequest`
255
+ :returns: Inside brain state of the manipulator and an error message if any.
256
+ :rtype: :class:`vbl_aquarium.models.ephys_link.BooleanStateResponse`
257
+ """
258
+ try:
259
+ if request.inside:
260
+ self._inside_brain.add(request.manipulator_id)
261
+ else:
262
+ self._inside_brain.discard(request.manipulator_id)
263
+ except Exception as e:
264
+ self._console.exception_error_print("Set Inside Brain", e)
265
+ return BooleanStateResponse(error=self._console.pretty_exception(e))
266
+ else:
267
+ return BooleanStateResponse(state=request.inside)
268
+
269
+ async def stop(self, manipulator_id: str) -> str:
270
+ """Stop a manipulator.
271
+
272
+ :param manipulator_id: Manipulator ID.
273
+ :type manipulator_id: str
274
+ :returns: Error message if any.
275
+ :rtype: str
276
+ """
277
+ try:
278
+ await self._bindings.stop(manipulator_id)
279
+ except Exception as e:
280
+ self._console.exception_error_print("Stop", e)
281
+ return self._console.pretty_exception(e)
282
+ else:
283
+ return ""
284
+
285
+ async def stop_all(self) -> str:
286
+ """Stop all manipulators.
287
+
288
+ :returns: Error message if any.
289
+ :rtype: str
290
+ """
291
+ try:
292
+ for manipulator_id in await self._bindings.get_manipulators():
293
+ await self._bindings.stop(manipulator_id)
294
+ except Exception as e:
295
+ self._console.exception_error_print("Stop", e)
296
+ return self._console.pretty_exception(e)
297
+ else:
298
+ return ""