ephys-link 1.2.7__tar.gz → 1.3.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 (33) hide show
  1. {ephys_link-1.2.7 → ephys_link-1.3.0}/PKG-INFO +7 -7
  2. {ephys_link-1.2.7 → ephys_link-1.3.0}/pyproject.toml +6 -6
  3. ephys_link-1.3.0/src/ephys_link/__about__.py +1 -0
  4. ephys_link-1.3.0/src/ephys_link/common.py +49 -0
  5. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/platform_handler.py +112 -128
  6. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/platform_manipulator.py +1 -0
  7. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/platforms/new_scale_handler.py +43 -37
  8. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/platforms/new_scale_manipulator.py +68 -69
  9. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/platforms/new_scale_pathfinder_handler.py +33 -32
  10. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/platforms/sensapex_handler.py +39 -36
  11. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/platforms/sensapex_manipulator.py +57 -62
  12. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/platforms/ump3_handler.py +17 -15
  13. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/platforms/ump3_manipulator.py +44 -42
  14. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/server.py +54 -62
  15. ephys_link-1.2.7/src/ephys_link/__about__.py +0 -1
  16. ephys_link-1.2.7/src/ephys_link/common.py +0 -226
  17. {ephys_link-1.2.7 → ephys_link-1.3.0}/.gitignore +0 -0
  18. {ephys_link-1.2.7 → ephys_link-1.3.0}/LICENSE +0 -0
  19. {ephys_link-1.2.7 → ephys_link-1.3.0}/README.md +0 -0
  20. {ephys_link-1.2.7 → ephys_link-1.3.0}/assets/icon.ico +0 -0
  21. {ephys_link-1.2.7 → ephys_link-1.3.0}/ephys_link.spec +0 -0
  22. {ephys_link-1.2.7 → ephys_link-1.3.0}/scripts/__init__.py +0 -0
  23. {ephys_link-1.2.7 → ephys_link-1.3.0}/scripts/move_tester.py +0 -0
  24. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/__init__.py +0 -0
  25. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/__main__.py +0 -0
  26. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/emergency_stop.py +0 -0
  27. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/gui.py +0 -0
  28. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/platforms/__init__.py +0 -0
  29. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/resources/CP210xManufacturing.dll +0 -0
  30. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/resources/NstMotorCtrl.dll +0 -0
  31. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/resources/SiUSBXp.dll +0 -0
  32. {ephys_link-1.2.7 → ephys_link-1.3.0}/src/ephys_link/resources/libum.dll +0 -0
  33. {ephys_link-1.2.7 → ephys_link-1.3.0}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: ephys-link
3
- Version: 1.2.7
3
+ Version: 1.3.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
@@ -14,7 +14,7 @@ Classifier: Intended Audience :: End Users/Desktop
14
14
  Classifier: Intended Audience :: Healthcare Industry
15
15
  Classifier: Intended Audience :: Science/Research
16
16
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
17
- Classifier: Operating System :: OS Independent
17
+ Classifier: Operating System :: Microsoft :: Windows
18
18
  Classifier: Programming Language :: Python
19
19
  Classifier: Programming Language :: Python :: 3
20
20
  Classifier: Programming Language :: Python :: 3.8
@@ -26,14 +26,14 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
26
26
  Classifier: Programming Language :: Python :: Implementation :: PyPy
27
27
  Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
28
28
  Requires-Python: <3.13,>=3.8
29
- Requires-Dist: aiohttp==3.9.3
30
- Requires-Dist: platformdirs==4.2.0
29
+ Requires-Dist: aiohttp==3.9.5
30
+ Requires-Dist: platformdirs==4.2.1
31
31
  Requires-Dist: pyserial==3.5
32
- Requires-Dist: python-socketio==5.11.1
32
+ Requires-Dist: python-socketio==5.11.2
33
33
  Requires-Dist: pythonnet==3.0.3
34
34
  Requires-Dist: requests==2.31.0
35
35
  Requires-Dist: sensapex==1.400.0
36
- Requires-Dist: wheel==0.42.0
36
+ Requires-Dist: vbl-aquarium==0.0.13
37
37
  Description-Content-Type: text/markdown
38
38
 
39
39
  # Electrophysiology Manipulator Link
@@ -23,21 +23,21 @@ classifiers = [
23
23
  "Programming Language :: Python :: Implementation :: PyPy",
24
24
  "Programming Language :: Python :: 3",
25
25
  "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
26
- "Operating System :: OS Independent",
26
+ "Operating System :: Microsoft :: Windows",
27
27
  "Intended Audience :: End Users/Desktop",
28
28
  "Intended Audience :: Healthcare Industry",
29
29
  "Intended Audience :: Science/Research",
30
30
  "Topic :: Scientific/Engineering :: Medical Science Apps.",
31
31
  ]
32
32
  dependencies = [
33
- "aiohttp==3.9.3",
34
- "platformdirs==4.2.0",
33
+ "aiohttp==3.9.5",
34
+ "platformdirs==4.2.1",
35
35
  "pyserial==3.5",
36
- "python-socketio==5.11.1",
36
+ "python-socketio==5.11.2",
37
37
  "pythonnet==3.0.3",
38
38
  "requests==2.31.0",
39
- "wheel==0.42.0",
40
39
  "sensapex==1.400.0",
40
+ "vbl-aquarium==0.0.13"
41
41
  ]
42
42
 
43
43
  [project.urls]
@@ -61,7 +61,7 @@ python = "3.12"
61
61
  dependencies = [
62
62
  "coverage[toml]>=6.5",
63
63
  "pytest",
64
- "python-socketio[client]==5.11.1",
64
+ "python-socketio[client]==5.11.2",
65
65
  ]
66
66
  [tool.hatch.envs.default.scripts]
67
67
  test = "pytest {args:tests}"
@@ -0,0 +1 @@
1
+ __version__ = "1.3.0"
@@ -0,0 +1,49 @@
1
+ """Commonly used functions and dictionaries
2
+
3
+ Contains globally used helper functions and typed dictionaries (to be used as
4
+ callback parameters)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ from vbl_aquarium.models.unity import Vector4
13
+
14
+ # Debugging flag
15
+ DEBUG = False
16
+
17
+ # Ephys Link ASCII
18
+ ASCII = r"""
19
+ ______ _ _ _ _
20
+ | ____| | | | | (_) | |
21
+ | |__ _ __ | |__ _ _ ___ | | _ _ __ | | __
22
+ | __| | '_ \| '_ \| | | / __| | | | | '_ \| |/ /
23
+ | |____| |_) | | | | |_| \__ \ | |____| | | | | <
24
+ |______| .__/|_| |_|\__, |___/ |______|_|_| |_|_|\_\
25
+ | | __/ |
26
+ |_| |___/
27
+ """
28
+
29
+
30
+ def dprint(message: str) -> None:
31
+ """Print message if debug is enabled.
32
+
33
+ :param message: Message to print.
34
+ :type message: str
35
+ :return: None
36
+ """
37
+ if DEBUG:
38
+ print(message)
39
+
40
+
41
+ def vector4_to_array(vector4: Vector4) -> list[float]:
42
+ """Convert a Vector4 to a list of floats.
43
+
44
+ :param vector4: Vector4 to convert.
45
+ :type vector4: :class:`vbl_aquarium.models.unity.Vector4`
46
+ :return: List of floats.
47
+ :rtype: list[float]
48
+ """
49
+ return [vector4.x, vector4.y, vector4.z, vector4.w]
@@ -17,6 +17,20 @@ from __future__ import annotations
17
17
  from abc import ABC, abstractmethod
18
18
  from typing import TYPE_CHECKING
19
19
 
20
+ from vbl_aquarium.models.ephys_link import (
21
+ AngularResponse,
22
+ BooleanStateResponse,
23
+ CanWriteRequest,
24
+ DriveToDepthRequest,
25
+ DriveToDepthResponse,
26
+ GetManipulatorsResponse,
27
+ GotoPositionRequest,
28
+ InsideBrainRequest,
29
+ PositionalResponse,
30
+ ShankCountResponse,
31
+ )
32
+ from vbl_aquarium.models.unity import Vector4
33
+
20
34
  from ephys_link import common as com
21
35
 
22
36
  if TYPE_CHECKING:
@@ -30,14 +44,14 @@ class PlatformHandler(ABC):
30
44
  """Initialize the manipulator handler with a dictionary of manipulators."""
31
45
 
32
46
  # Registered manipulators are stored as a dictionary of IDs (string) to
33
- # manipulator objects
47
+ # manipulator objects.
34
48
  self.manipulators = {}
35
49
  self.num_axes = 4
36
50
 
37
51
  # Platform axes dimensions in mm
38
- self.dimensions = [20, 20, 20, 20]
52
+ self.dimensions = Vector4(x=20, y=20, z=20, w=20)
39
53
 
40
- # Platform Handler Methods
54
+ # Platform Handler Methods.
41
55
 
42
56
  def reset(self) -> bool:
43
57
  """Reset handler.
@@ -65,21 +79,21 @@ class PlatformHandler(ABC):
65
79
  else:
66
80
  return True
67
81
 
68
- def get_manipulators(self) -> com.GetManipulatorsOutputData:
82
+ def get_manipulators(self) -> GetManipulatorsResponse:
69
83
  """Get all registered manipulators.
70
84
 
71
85
  :return: Result of connected manipulators, platform information, and error message (if any).
72
- :rtype: :class:`ephys_link.common.GetManipulatorsOutputData`
86
+ :rtype: :class:`vbl_aquarium.models.ephys_link.GetManipulatorsResponse`
73
87
  """
74
- error = "Error getting manipulators"
75
88
  try:
76
- devices = self._get_manipulators()
77
- error = ""
89
+ manipulators = self._get_manipulators()
78
90
  except Exception as e:
79
91
  print(f"[ERROR]\t\t Getting manipulators: {type(e)}: {e}\n")
80
- return com.GetManipulatorsOutputData([], self.num_axes, self.dimensions, error)
92
+ return GetManipulatorsResponse(error="Error getting manipulators")
81
93
  else:
82
- return com.GetManipulatorsOutputData(devices, self.num_axes, self.dimensions, error)
94
+ return GetManipulatorsResponse(
95
+ manipulators=manipulators, num_axes=self.num_axes, dimensions=self.dimensions
96
+ )
83
97
 
84
98
  def register_manipulator(self, manipulator_id: str) -> str:
85
99
  """Register a manipulator.
@@ -135,46 +149,46 @@ class PlatformHandler(ABC):
135
149
  com.dprint(f"[SUCCESS]\t Unregistered manipulator: {manipulator_id}\n")
136
150
  return ""
137
151
 
138
- def get_pos(self, manipulator_id: str) -> com.PositionalOutputData:
152
+ def get_pos(self, manipulator_id: str) -> PositionalResponse:
139
153
  """Get the current position of a manipulator.
140
154
 
141
155
  :param manipulator_id: The ID of the manipulator to get the position of.
142
156
  :type manipulator_id: str
143
157
  :return: Positional information for the manipulator and error message (if any).
144
- :rtype: :class:`ephys_link.common.PositionalOutputData`
158
+ :rtype: :class:`vbl_aquarium.models.ephys_link.PositionalResponse`
145
159
  """
146
160
  try:
147
- # Check calibration status
161
+ # Check calibration status.
148
162
  if (
149
163
  hasattr(self.manipulators[manipulator_id], "get_calibrated")
150
164
  and not self.manipulators[manipulator_id].get_calibrated()
151
165
  ):
152
166
  print(f"[ERROR]\t\t Calibration not complete: {manipulator_id}\n")
153
- return com.PositionalOutputData([], "Manipulator not calibrated")
167
+ return PositionalResponse(error="Manipulator not calibrated")
154
168
 
155
- # Get position and convert to unified space
169
+ # Get position and convert to unified space.
156
170
  manipulator_pos = self._get_pos(manipulator_id)
157
171
 
158
- # Shortcut return for Pathfinder
172
+ # Shortcut return for Pathfinder.
159
173
  if self.num_axes == -1:
160
174
  return manipulator_pos
161
175
 
162
- # Error check and convert position to unified space
163
- if manipulator_pos["error"] != "":
164
- return manipulator_pos
165
- return com.PositionalOutputData(self._platform_space_to_unified_space(manipulator_pos["position"]), "")
176
+ # Convert position to unified space.
177
+ return manipulator_pos.model_copy(
178
+ update={"position": self._platform_space_to_unified_space(manipulator_pos.position)}
179
+ )
166
180
  except KeyError:
167
- # Manipulator not found in registered manipulators
181
+ # Manipulator not found in registered manipulators.
168
182
  print(f"[ERROR]\t\t Manipulator not registered: {manipulator_id}")
169
- return com.PositionalOutputData([], "Manipulator not registered")
183
+ return PositionalResponse(error="Manipulator not registered")
170
184
 
171
- def get_angles(self, manipulator_id: str) -> com.AngularOutputData:
185
+ def get_angles(self, manipulator_id: str) -> AngularResponse:
172
186
  """Get the current position of a manipulator.
173
187
 
174
188
  :param manipulator_id: The ID of the manipulator to get the angles of.
175
189
  :type manipulator_id: str
176
190
  :return: Angular information for the manipulator and error message (if any).
177
- :rtype: :class:`ephys_link.common.AngularOutputData`
191
+ :rtype: :class:`vbl_aquarium.models.ephys_link.AngularResponse`
178
192
  """
179
193
  try:
180
194
  # Check calibration status
@@ -183,7 +197,7 @@ class PlatformHandler(ABC):
183
197
  and not self.manipulators[manipulator_id].get_calibrated()
184
198
  ):
185
199
  print(f"[ERROR]\t\t Calibration not complete: {manipulator_id}\n")
186
- return com.AngularOutputData([], "Manipulator not calibrated")
200
+ return AngularResponse(error="Manipulator not calibrated")
187
201
 
188
202
  # Get position
189
203
  return self._get_angles(manipulator_id)
@@ -191,124 +205,109 @@ class PlatformHandler(ABC):
191
205
  except KeyError:
192
206
  # Manipulator not found in registered manipulators
193
207
  print(f"[ERROR]\t\t Manipulator not registered: {manipulator_id}")
194
- return com.AngularOutputData([], "Manipulator not registered")
208
+ return AngularResponse(error="Manipulator not registered")
195
209
 
196
- def get_shank_count(self, manipulator_id: str) -> com.ShankCountOutputData:
210
+ def get_shank_count(self, manipulator_id: str) -> ShankCountResponse:
197
211
  """Get the number of shanks on the probe
198
212
 
199
213
  :param manipulator_id: The ID of the manipulator to get the number of shanks of.
200
214
  :type manipulator_id: str
201
215
  :return: Number of shanks on the probe.
202
- :rtype: :class:`ephys_link.common.ShankCountOutputData`
216
+ :rtype: :class:`vbl_aquarium.models.ephys_link.ShankCountResponse`
203
217
  """
204
218
  return self._get_shank_count(manipulator_id)
205
219
 
206
- async def goto_pos(self, manipulator_id: str, position: list[float], speed: float) -> com.PositionalOutputData:
220
+ async def goto_pos(self, request: GotoPositionRequest) -> PositionalResponse:
207
221
  """Move manipulator to position
208
222
 
209
- :param manipulator_id: The ID of the manipulator to move
210
- :type manipulator_id: str
211
- :param position: The position to move to in (x, y, z, w) in mm
212
- :type position: list[float]
213
- :param speed: The speed to move at (in mm/s)
214
- :type speed: float
223
+ :param request: The goto request parsed from the server.
224
+ :type request: :class:`vbl_aquarium.models.ephys_link.GotoPositionRequest`
215
225
  :return: Resulting position of the manipulator and error message (if any).
216
- :rtype: :class:`ephys_link.common.PositionalOutputData`
226
+ :rtype: :class:`vbl_aquarium.models.ephys_link.PositionalResponse`
217
227
  """
218
228
  try:
219
- # Check calibration status
220
- if not self.manipulators[manipulator_id].get_calibrated():
221
- print(f"[ERROR]\t\t Calibration not complete: {manipulator_id}\n")
222
- return com.PositionalOutputData([], "Manipulator not calibrated")
229
+ # Check calibration status.
230
+ if not self.manipulators[request.manipulator_id].get_calibrated():
231
+ print(f"[ERROR]\t\t Calibration not complete: {request.manipulator_id}\n")
232
+ return PositionalResponse(error="Manipulator not calibrated")
223
233
 
224
- # Check write state
225
- if not self.manipulators[manipulator_id].get_can_write():
226
- print(f"[ERROR]\t\t Cannot write to manipulator: {manipulator_id}")
227
- return com.PositionalOutputData([], "Cannot write to manipulator")
234
+ # Check write state.
235
+ if not self.manipulators[request.manipulator_id].get_can_write():
236
+ print(f"[ERROR]\t\t Cannot write to manipulator: {request.manipulator_id}")
237
+ return PositionalResponse(error="Cannot write to manipulator")
228
238
 
229
239
  # Convert position to platform space, move, and convert final position back to
230
- # unified space
231
- end_position = await self._goto_pos(manipulator_id, self._unified_space_to_platform_space(position), speed)
232
- if end_position["error"] != "":
233
- return end_position
234
- return com.PositionalOutputData(self._platform_space_to_unified_space(end_position["position"]), "")
235
-
240
+ # unified space.
241
+ end_position = await self._goto_pos(
242
+ request.model_copy(update={"position": self._unified_space_to_platform_space(request.position)})
243
+ )
244
+ return end_position.model_copy(
245
+ update={"position": self._platform_space_to_unified_space(end_position.position)}
246
+ )
236
247
  except KeyError:
237
- # Manipulator not found in registered manipulators
238
- print(f"[ERROR]\t\t Manipulator not registered: {manipulator_id}\n")
239
- return com.PositionalOutputData([], "Manipulator not registered")
248
+ # Manipulator not found in registered manipulators.
249
+ print(f"[ERROR]\t\t Manipulator not registered: {request.manipulator_id}\n")
250
+ return PositionalResponse(error="Manipulator not registered")
240
251
 
241
- async def drive_to_depth(self, manipulator_id: str, depth: float, speed: float) -> com.DriveToDepthOutputData:
252
+ async def drive_to_depth(self, request: DriveToDepthRequest) -> DriveToDepthResponse:
242
253
  """Drive manipulator to depth
243
254
 
244
- :param manipulator_id: The ID of the manipulator to drive
245
- :type manipulator_id: str
246
- :param depth: The depth to drive to in mm
247
- :type depth: float
248
- :param speed: The speed to drive at (in mm/s)
249
- :type speed: float
255
+ :param request: The drive to depth request parsed from the server.
256
+ :type request: :class:`vbl_aquarium.models.ephys_link.DriveToDepthRequest`
250
257
  :return: Resulting depth of the manipulator and error message (if any).
251
258
  :rtype: :class:`ephys_link.common.DriveToDepthOutputData`
252
259
  """
253
260
  try:
254
261
  # Check calibration status
255
- if not self.manipulators[manipulator_id].get_calibrated():
256
- print(f"[ERROR]\t\t Calibration not complete: {manipulator_id}\n")
257
- return com.DriveToDepthOutputData(0, "Manipulator not calibrated")
262
+ if not self.manipulators[request.manipulator_id].get_calibrated():
263
+ print(f"[ERROR]\t\t Calibration not complete: {request.manipulator_id}\n")
264
+ return DriveToDepthResponse(error="Manipulator not calibrated")
258
265
 
259
266
  # Check write state
260
- if not self.manipulators[manipulator_id].get_can_write():
261
- print(f"[ERROR]\t\t Cannot write to manipulator: {manipulator_id}")
262
- return com.DriveToDepthOutputData(0, "Cannot write to manipulator")
267
+ if not self.manipulators[request.manipulator_id].get_can_write():
268
+ print(f"[ERROR]\t\t Cannot write to manipulator: {request.manipulator_id}")
269
+ return DriveToDepthResponse(error="Cannot write to manipulator")
263
270
 
264
271
  end_depth = await self._drive_to_depth(
265
- manipulator_id,
266
- self._unified_space_to_platform_space([0, 0, 0, depth])[3],
267
- speed,
272
+ request.model_copy(update={"depth": self._unified_space_to_platform_space(Vector4(w=request.depth)).w})
268
273
  )
269
- if end_depth["error"] != "":
270
- return end_depth
271
- return com.DriveToDepthOutputData(
272
- self._platform_space_to_unified_space([0, 0, 0, end_depth["depth"]])[3],
273
- "",
274
+ return end_depth.model_copy(
275
+ update={"depth": self._platform_space_to_unified_space(Vector4(w=end_depth.depth)).w}
274
276
  )
275
-
276
277
  except KeyError:
277
278
  # Manipulator not found in registered manipulators
278
- print(f"[ERROR]\t\t Manipulator not registered: {manipulator_id}\n")
279
- return com.DriveToDepthOutputData(0, "Manipulator " "not registered")
279
+ print(f"[ERROR]\t\t Manipulator not registered: {request.manipulator_id}\n")
280
+ return DriveToDepthResponse(error="Manipulator not registered")
280
281
 
281
- def set_inside_brain(self, manipulator_id: str, inside: bool) -> com.StateOutputData:
282
+ def set_inside_brain(self, request: InsideBrainRequest) -> BooleanStateResponse:
282
283
  """Set manipulator inside brain state (restricts motion)
283
284
 
284
- :param manipulator_id: The ID of the manipulator to set the state of
285
- :type manipulator_id: str
286
- :param inside: True if inside brain, False if outside
287
- :type inside: bool
285
+ :param request: The inside brain request parsed from the server.
286
+ :type request: :class:`vbl_aquarium.models.ephys_link.InsideBrainRequest`
288
287
  :return: New inside brain state of the manipulator and error message (if any).
289
- :rtype: :class:`ephys_link.common.StateOutputData`
288
+ :rtype: :class:`vbl_aquarium.models.ephys_link.BooleanStateResponse`
290
289
  """
291
290
  try:
292
291
  # Check calibration status
293
292
  if (
294
- hasattr(self.manipulators[manipulator_id], "get_calibrated")
295
- and not self.manipulators[manipulator_id].get_calibrated()
293
+ hasattr(self.manipulators[request.manipulator_id], "get_calibrated")
294
+ and not self.manipulators[request.manipulator_id].get_calibrated()
296
295
  ):
297
296
  print("[ERROR]\t\t Calibration not complete\n")
298
- return com.StateOutputData(False, "Manipulator not calibrated")
297
+ return BooleanStateResponse(error="Manipulator not calibrated")
299
298
 
300
- return self._set_inside_brain(manipulator_id, inside)
299
+ return self._set_inside_brain(request)
301
300
 
302
301
  except KeyError:
303
302
  # Manipulator not found in registered manipulators
304
- print(f"[ERROR]\t\t Manipulator {manipulator_id} not registered\n")
305
- return com.StateOutputData(False, "Manipulator not " "registered")
303
+ print(f"[ERROR]\t\t Manipulator {request.manipulator_id} not registered\n")
304
+ return BooleanStateResponse(error="Manipulator not " "registered")
306
305
 
307
306
  except Exception as e:
308
307
  # Other error
309
- print(f"[ERROR]\t\t Set manipulator {manipulator_id} inside brain " f"state")
308
+ print(f"[ERROR]\t\t Set manipulator {request.manipulator_id} inside brain " f"state")
310
309
  print(f"{e}\n")
311
- return com.StateOutputData(False, "Error setting " "inside brain")
310
+ return BooleanStateResponse(error="Error setting inside brain")
312
311
 
313
312
  async def calibrate(self, manipulator_id: str, sio: socketio.AsyncServer) -> str:
314
313
  """Calibrate manipulator
@@ -364,35 +363,26 @@ class PlatformHandler(ABC):
364
363
 
365
364
  def set_can_write(
366
365
  self,
367
- manipulator_id: str,
368
- can_write: bool,
369
- hours: float,
370
- sio: socketio.AsyncServer,
371
- ) -> com.StateOutputData:
366
+ request: CanWriteRequest,
367
+ ) -> BooleanStateResponse:
372
368
  """Set manipulator can_write state (enables/disabled moving manipulator)
373
369
 
374
- :param manipulator_id: The ID of the manipulator to set the state of
375
- :type manipulator_id: str
376
- :param can_write: True if allowed to move, False if outside
377
- :type can_write: bool
378
- :param hours: The number of hours to allow writing (0 = forever)
379
- :type hours: float
380
- :param sio: SocketIO object from server to emit reset event
381
- :type sio: :class:`socketio.AsyncServer`
370
+ :param request: The can write request parsed from the server.
371
+ :type request: :class:`vbl_aquarium.models.ephys_link.CanWriteRequest`
382
372
  :return: New can_write state of the manipulator and error message (if any).
383
373
  :rtype: :class:`ephys_link.common.StateOutputData`
384
374
  """
385
375
  try:
386
- return self._set_can_write(manipulator_id, can_write, hours, sio)
376
+ return self._set_can_write(request)
387
377
  except KeyError:
388
378
  # Manipulator not found in registered manipulators
389
- print(f"[ERROR]\t\t Manipulator not registered: {manipulator_id}\n")
390
- return com.StateOutputData(False, "Manipulator not " "registered")
379
+ print(f"[ERROR]\t\t Manipulator not registered: {request.manipulator_id}\n")
380
+ return BooleanStateResponse(error="Manipulator not registered")
391
381
  except Exception as e:
392
382
  # Other error
393
- print(f"[ERROR]\t\t Set manipulator {manipulator_id} can_write state")
383
+ print(f"[ERROR]\t\t Set manipulator {request.manipulator_id} can_write state")
394
384
  print(f"{e}\n")
395
- return com.StateOutputData(False, "Error setting " "can_write")
385
+ return BooleanStateResponse(error="Error setting can_write")
396
386
 
397
387
  # Platform specific methods to override
398
388
 
@@ -409,27 +399,27 @@ class PlatformHandler(ABC):
409
399
  raise NotImplementedError
410
400
 
411
401
  @abstractmethod
412
- def _get_pos(self, manipulator_id: str) -> com.PositionalOutputData:
402
+ def _get_pos(self, manipulator_id: str) -> PositionalResponse:
413
403
  raise NotImplementedError
414
404
 
415
405
  @abstractmethod
416
- def _get_angles(self, manipulator_id: str) -> com.AngularOutputData:
406
+ def _get_angles(self, manipulator_id: str) -> AngularResponse:
417
407
  raise NotImplementedError
418
408
 
419
409
  @abstractmethod
420
- def _get_shank_count(self, manipulator_id: str) -> com.ShankCountOutputData:
410
+ def _get_shank_count(self, manipulator_id: str) -> ShankCountResponse:
421
411
  raise NotImplementedError
422
412
 
423
413
  @abstractmethod
424
- async def _goto_pos(self, manipulator_id: str, position: list[float], speed: float) -> com.PositionalOutputData:
414
+ async def _goto_pos(self, request: GotoPositionRequest) -> PositionalResponse:
425
415
  raise NotImplementedError
426
416
 
427
417
  @abstractmethod
428
- async def _drive_to_depth(self, manipulator_id: str, depth: float, speed: float) -> com.DriveToDepthOutputData:
418
+ async def _drive_to_depth(self, request: DriveToDepthRequest) -> DriveToDepthResponse:
429
419
  raise NotImplementedError
430
420
 
431
421
  @abstractmethod
432
- def _set_inside_brain(self, manipulator_id: str, inside: bool) -> com.StateOutputData:
422
+ def _set_inside_brain(self, request: InsideBrainRequest) -> BooleanStateResponse:
433
423
  raise NotImplementedError
434
424
 
435
425
  @abstractmethod
@@ -449,33 +439,27 @@ class PlatformHandler(ABC):
449
439
  raise NotImplementedError
450
440
 
451
441
  @abstractmethod
452
- def _set_can_write(
453
- self,
454
- manipulator_id: str,
455
- can_write: bool,
456
- hours: float,
457
- sio: socketio.AsyncServer,
458
- ) -> com.StateOutputData:
442
+ def _set_can_write(self, request: CanWriteRequest) -> BooleanStateResponse:
459
443
  raise NotImplementedError
460
444
 
461
445
  @abstractmethod
462
- def _platform_space_to_unified_space(self, platform_position: list[float]) -> list[float]:
446
+ def _platform_space_to_unified_space(self, platform_position: Vector4) -> Vector4:
463
447
  """Convert position in platform space to position in unified manipulator space
464
448
 
465
449
  :param platform_position: Position in platform space (x, y, z, w) in mm
466
- :type platform_position: list[float]
450
+ :type platform_position: Vector4
467
451
  :return: Position in unified manipulator space (x, y, z, w) in mm
468
- :rtype: list[float]
452
+ :rtype: Vector4
469
453
  """
470
454
  raise NotImplementedError
471
455
 
472
456
  @abstractmethod
473
- def _unified_space_to_platform_space(self, unified_position: list[float]) -> list[float]:
457
+ def _unified_space_to_platform_space(self, unified_position: Vector4) -> Vector4:
474
458
  """Convert position in unified manipulator space to position in platform space
475
459
 
476
460
  :param unified_position: Position in unified manipulator space (x, y, z, w) in mm
477
- :type unified_position: list[float]
461
+ :type unified_position: Vector4
478
462
  :return: Position in platform space (x, y, z, w) in mm
479
- :rtype: list[float]
463
+ :rtype: Vector4
480
464
  """
481
465
  raise NotImplementedError
@@ -3,6 +3,7 @@
3
3
  Most functionality will be implemented on the platform handler side. This is mostly
4
4
  for enforcing implementation of the stop method and hold common properties.
5
5
  """
6
+
6
7
  from abc import ABC, abstractmethod
7
8
 
8
9
  # Constants