ephys-link 1.3.0b2__py3-none-any.whl → 2.0.0__py3-none-any.whl

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 (42) hide show
  1. ephys_link/__about__.py +1 -1
  2. ephys_link/__main__.py +51 -90
  3. ephys_link/back_end/__init__.py +0 -0
  4. ephys_link/back_end/platform_handler.py +315 -0
  5. ephys_link/back_end/server.py +274 -0
  6. ephys_link/bindings/__init__.py +0 -0
  7. ephys_link/bindings/fake_binding.py +84 -0
  8. ephys_link/bindings/mpm_binding.py +315 -0
  9. ephys_link/bindings/ump_4_binding.py +157 -0
  10. ephys_link/front_end/__init__.py +0 -0
  11. ephys_link/front_end/cli.py +104 -0
  12. ephys_link/front_end/gui.py +204 -0
  13. ephys_link/utils/__init__.py +0 -0
  14. ephys_link/utils/base_binding.py +176 -0
  15. ephys_link/utils/console.py +127 -0
  16. ephys_link/utils/constants.py +23 -0
  17. ephys_link/utils/converters.py +86 -0
  18. ephys_link/utils/startup.py +65 -0
  19. ephys_link-2.0.0.dist-info/METADATA +91 -0
  20. ephys_link-2.0.0.dist-info/RECORD +25 -0
  21. {ephys_link-1.3.0b2.dist-info → ephys_link-2.0.0.dist-info}/WHEEL +1 -1
  22. {ephys_link-1.3.0b2.dist-info → ephys_link-2.0.0.dist-info}/licenses/LICENSE +674 -674
  23. ephys_link/common.py +0 -49
  24. ephys_link/emergency_stop.py +0 -67
  25. ephys_link/gui.py +0 -163
  26. ephys_link/platform_handler.py +0 -465
  27. ephys_link/platform_manipulator.py +0 -34
  28. ephys_link/platforms/__init__.py +0 -5
  29. ephys_link/platforms/new_scale_handler.py +0 -141
  30. ephys_link/platforms/new_scale_manipulator.py +0 -312
  31. ephys_link/platforms/new_scale_pathfinder_handler.py +0 -235
  32. ephys_link/platforms/sensapex_handler.py +0 -151
  33. ephys_link/platforms/sensapex_manipulator.py +0 -221
  34. ephys_link/platforms/ump3_handler.py +0 -57
  35. ephys_link/platforms/ump3_manipulator.py +0 -145
  36. ephys_link/resources/CP210xManufacturing.dll +0 -0
  37. ephys_link/resources/NstMotorCtrl.dll +0 -0
  38. ephys_link/resources/SiUSBXp.dll +0 -0
  39. ephys_link/server.py +0 -436
  40. ephys_link-1.3.0b2.dist-info/METADATA +0 -163
  41. ephys_link-1.3.0b2.dist-info/RECORD +0 -26
  42. {ephys_link-1.3.0b2.dist-info → ephys_link-2.0.0.dist-info}/entry_points.txt +0 -0
@@ -1,312 +0,0 @@
1
- """New Scale Manipulator class
2
-
3
- Handles logic for calling New Scale API functions. Also includes extra logic for safe
4
- function calls, error handling, managing per-manipulator attributes, and returning the
5
- appropriate callback parameters like in :mod:`ephys_link.new_scale_handler`.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import asyncio
11
- import threading
12
- from typing import TYPE_CHECKING
13
-
14
- from vbl_aquarium.models.ephys_link import (
15
- CanWriteRequest,
16
- DriveToDepthRequest,
17
- DriveToDepthResponse,
18
- GotoPositionRequest,
19
- PositionalResponse,
20
- )
21
- from vbl_aquarium.models.unity import Vector4
22
-
23
- import ephys_link.common as com
24
- from ephys_link.common import vector4_to_array
25
- from ephys_link.platform_manipulator import (
26
- HOURS_TO_SECONDS,
27
- MM_TO_UM,
28
- POSITION_POLL_DELAY,
29
- PlatformManipulator,
30
- )
31
-
32
- if TYPE_CHECKING:
33
- # noinspection PyUnresolvedReferences
34
- from NstMotorCtrl import NstCtrlAxis
35
-
36
- # Constants
37
- ACCELERATION_MULTIPLIER = 5
38
- CUTOFF_MULTIPLIER = 0.005
39
- AT_TARGET_FLAG = 0x040000
40
-
41
-
42
- class NewScaleManipulator(PlatformManipulator):
43
- def __init__(
44
- self,
45
- manipulator_id: str,
46
- x_axis: NstCtrlAxis,
47
- y_axis: NstCtrlAxis,
48
- z_axis: NstCtrlAxis,
49
- ) -> None:
50
- """Construct a new Manipulator object
51
-
52
- :param manipulator_id: Manipulator ID
53
- :param x_axis: X axis object
54
- :param y_axis: Y axis object
55
- :param z_axis: Z axis object
56
- """
57
-
58
- super().__init__()
59
- self._id = manipulator_id
60
- self._movement_tolerance = 0.01
61
- self._x = x_axis
62
- self._y = y_axis
63
- self._z = z_axis
64
- self._axes = [self._x, self._y, self._z]
65
-
66
- # Set to CL control
67
- self._x.SetCL_Enable(True)
68
- self._y.SetCL_Enable(True)
69
- self._z.SetCL_Enable(True)
70
-
71
- def query_all_axes(self):
72
- """Query all axes for their position and status"""
73
- for axis in self._axes:
74
- axis.QueryPosStatus()
75
-
76
- def get_pos(self) -> PositionalResponse:
77
- """Get the current position of the manipulator and convert it into mm.
78
-
79
- :return: Position of manipulator in (x, y, z, z) in mm (or an empty array on error) and error message (if any).
80
- :rtype: :class:`ephys_link.common.PositionalOutputData`
81
- """
82
- self.query_all_axes()
83
-
84
- # Get position data and convert from µm to mm
85
- try:
86
- # com.dprint(f"[SUCCESS]\t Got position of manipulator {self._id}\n")
87
- return PositionalResponse(
88
- position=Vector4(
89
- x=self._x.CurPosition / MM_TO_UM,
90
- y=self._y.CurPosition / MM_TO_UM,
91
- z=self._z.CurPosition / MM_TO_UM,
92
- w=self._z.CurPosition / MM_TO_UM,
93
- )
94
- )
95
- except Exception as e:
96
- print(f"[ERROR]\t\t Getting position of manipulator {self._id}")
97
- print(f"{e}\n")
98
- return PositionalResponse(error="Error getting position")
99
-
100
- async def goto_pos(self, request: GotoPositionRequest) -> PositionalResponse:
101
- """Move manipulator to position.
102
-
103
- :param request: The goto request parsed from the server.
104
- :type request: :class:`vbl_aquarium.models.ephys_link.GotoPositionRequest`
105
- :return: Resulting position of manipulator in (x, y, z, z) in mm (or an empty array on error)
106
- and error message (if any).
107
- :rtype: :class:`ephys_link.common.PositionalOutputData`
108
- """
109
- # Check if able to write
110
- if not self._can_write:
111
- print(f"[ERROR]\t\t Manipulator {self._id} movement canceled")
112
- return PositionalResponse(error="Manipulator movement canceled")
113
-
114
- # Stop current movement
115
- if self._is_moving:
116
- for axis in self._axes:
117
- axis.Stop()
118
- self._is_moving = False
119
-
120
- try:
121
- target_position_um = request.position * MM_TO_UM
122
-
123
- # Restrict target position to just z-axis if inside brain
124
- if self._inside_brain:
125
- z_axis = target_position_um.z
126
- target_position_um = target_position_um.model_copy(
127
- update={**self.get_pos().position.model_dump(), "z": z_axis}
128
- )
129
-
130
- # Mark movement as started
131
- self._is_moving = True
132
-
133
- # Send move command
134
- speed_um = request.speed * MM_TO_UM
135
- for i in range(3):
136
- self._axes[i].SetCL_Speed(
137
- speed_um,
138
- speed_um * ACCELERATION_MULTIPLIER,
139
- speed_um * CUTOFF_MULTIPLIER,
140
- )
141
- self._axes[i].MoveAbsolute(vector4_to_array(target_position_um)[i])
142
-
143
- # Check and wait for completion (while able to write)
144
- self.query_all_axes()
145
- while (
146
- not (self._x.CurStatus & AT_TARGET_FLAG)
147
- or not (self._y.CurStatus & AT_TARGET_FLAG)
148
- or not (self._z.CurStatus & AT_TARGET_FLAG)
149
- ) and self._can_write:
150
- await asyncio.sleep(POSITION_POLL_DELAY)
151
- self.query_all_axes()
152
-
153
- # Get position
154
- final_position = self.get_pos().position
155
-
156
- # Mark movement as finished
157
- self._is_moving = False
158
-
159
- # Return success unless write was disabled during movement (meaning a stop occurred)
160
- if not self._can_write:
161
- com.dprint(f"[ERROR]\t\t Manipulator {self._id} movement canceled")
162
- return PositionalResponse(error="Manipulator movement canceled")
163
-
164
- # Return error if movement did not reach target.
165
- if not all(
166
- abs(axis) < self._movement_tolerance for axis in vector4_to_array(final_position - request.position)[:3]
167
- ):
168
- com.dprint(f"[ERROR]\t\t Manipulator {self._id} did not reach target position.")
169
- com.dprint(f"\t\t\t Expected: {request.position}, Got: {final_position}")
170
- return PositionalResponse(error="Manipulator did not reach target position")
171
-
172
- # Made it to the target.
173
- com.dprint(f"[SUCCESS]\t Moved manipulator {self._id} to position" f" {final_position}\n")
174
- return PositionalResponse(position=final_position)
175
- except Exception as e:
176
- print(f"[ERROR]\t\t Moving manipulator {self._id} to position {request.position}")
177
- print(f"{e}\n")
178
- return PositionalResponse(error="Error moving manipulator")
179
-
180
- async def drive_to_depth(self, request: DriveToDepthRequest) -> DriveToDepthResponse:
181
- """Drive the manipulator to a certain depth.
182
-
183
- :param request: The drive to depth request parsed from the server.
184
- :type request: :class:`vbl_aquarium.models.ephys_link.DriveToDepthRequest`
185
- :return: Resulting depth of manipulator in mm (or 0 on error) and error message (if any).
186
- :rtype: :class:`ephys_link.common.DriveToDepthOutputData`
187
- """
188
- # Check if able to write
189
- if not self._can_write:
190
- print(f"[ERROR]\t\t Manipulator {self._id} movement canceled")
191
- return DriveToDepthResponse(error="Manipulator movement canceled")
192
-
193
- # Stop current movement
194
- if self._is_moving:
195
- for axis in self._axes:
196
- axis.Stop()
197
- self._is_moving = False
198
-
199
- try:
200
- target_depth_um = request.depth * MM_TO_UM
201
-
202
- # Mark movement as started
203
- self._is_moving = True
204
-
205
- # Send move command to just z axis
206
- speed_um = request.speed * MM_TO_UM
207
- self._z.SetCL_Speed(
208
- speed_um,
209
- speed_um * ACCELERATION_MULTIPLIER,
210
- speed_um * CUTOFF_MULTIPLIER,
211
- )
212
- self._z.MoveAbsolute(target_depth_um)
213
-
214
- # Check and wait for completion (while able to write)
215
- self._z.QueryPosStatus()
216
- while not (self._z.CurStatus & AT_TARGET_FLAG) and self._can_write:
217
- await asyncio.sleep(0.1)
218
- self._z.QueryPosStatus()
219
-
220
- # Get position
221
- final_depth = self.get_pos().position.w
222
-
223
- # Mark movement as finished
224
- self._is_moving = False
225
-
226
- # Return success unless write was disabled during movement (meaning a stop occurred)
227
- if not self._can_write:
228
- com.dprint(f"[ERROR]\t\t Manipulator {self._id} movement canceled")
229
- return DriveToDepthResponse(error="Manipulator movement canceled")
230
-
231
- # Return error if movement did not reach target.
232
- if not abs(final_depth - request.depth) < self._movement_tolerance:
233
- com.dprint(f"[ERROR]\t\t Manipulator {self._id} did not reach target depth")
234
- com.dprint(f"\t\t\t Expected: {request.depth}, Got: {final_depth}")
235
- return DriveToDepthResponse(error="Manipulator did not reach target depth")
236
-
237
- # Made it to the target.
238
- com.dprint(f"[SUCCESS]\t Moved manipulator {self._id} to position" f" {final_depth}\n")
239
- return DriveToDepthResponse(depth=final_depth)
240
- except Exception as e:
241
- print(f"[ERROR]\t\t Moving manipulator {self._id} to depth {request.depth}")
242
- print(f"{e}\n")
243
- # Return 0 and error message on failure
244
- return DriveToDepthResponse(error="Error driving manipulator")
245
-
246
- def calibrate(self) -> bool:
247
- """Calibrate the manipulator.
248
-
249
- :return: None
250
- """
251
- return self._x.CalibrateFrequency() and self._y.CalibrateFrequency() and self._z.CalibrateFrequency()
252
-
253
- def get_calibrated(self) -> bool:
254
- """Return the calibration state of the manipulator.
255
-
256
- :return: True if the manipulator is calibrated, False otherwise.
257
- :rtype: bool
258
- """
259
- return self._calibrated
260
-
261
- def set_calibrated(self) -> None:
262
- """Set the manipulator to calibrated
263
-
264
- :return: None
265
- """
266
- self._calibrated = True
267
-
268
- def set_can_write(self, request: CanWriteRequest) -> None:
269
- """Set if the manipulator can move.
270
-
271
- :param request: The can write request parsed from the server.
272
- :type request: :class:`vbl_aquarium.models.ephys_link.CanWriteRequest`
273
- :return: None
274
- """
275
- self._can_write = request.can_write
276
-
277
- if request.can_write and request.hours > 0:
278
- if self._reset_timer:
279
- self._reset_timer.cancel()
280
- self._reset_timer = threading.Timer(request.hours * HOURS_TO_SECONDS, self.reset_can_write)
281
- self._reset_timer.start()
282
-
283
- def reset_can_write(self) -> None:
284
- """Reset the :attr:`can_write` flag."""
285
- self._can_write = False
286
-
287
- def get_can_write(self) -> bool:
288
- """Return if the manipulator can move.
289
-
290
- :return: True if the manipulator can move, False otherwise.
291
- :rtype: bool
292
- """
293
- return self._can_write
294
-
295
- def set_inside_brain(self, inside: bool) -> None:
296
- """Set if the manipulator is inside the brain.
297
-
298
- :param inside: True if the manipulator is inside the brain, False otherwise.
299
- :type inside: bool
300
- :return: None
301
- """
302
- self._inside_brain = inside
303
-
304
- def stop(self) -> None:
305
- """Stop all axes on manipulator.
306
-
307
- :returns None
308
- """
309
- for axis in self._axes:
310
- axis.Stop()
311
- self._can_write = False
312
- com.dprint(f"[SUCCESS]\t Stopped manipulator {self._id}\n")
@@ -1,235 +0,0 @@
1
- """Handle communications with New Scale's HTTP server
2
-
3
- Implements New Scale specific API calls.
4
-
5
- This is a subclass of :class:`ephys_link.platform_handler.PlatformHandler`.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import json
11
- from sys import exit
12
- from typing import TYPE_CHECKING
13
- from urllib import request
14
- from urllib.error import URLError
15
-
16
- from vbl_aquarium.models.ephys_link import (
17
- AngularResponse,
18
- BooleanStateResponse,
19
- CanWriteRequest,
20
- DriveToDepthRequest,
21
- DriveToDepthResponse,
22
- GotoPositionRequest,
23
- InsideBrainRequest,
24
- PositionalResponse,
25
- ShankCountResponse,
26
- )
27
- from vbl_aquarium.models.unity import Vector3, Vector4
28
-
29
- from ephys_link.platform_handler import PlatformHandler
30
-
31
- if TYPE_CHECKING:
32
- import socketio
33
-
34
-
35
- class NewScalePathfinderHandler(PlatformHandler):
36
- """Handler for New Scale HTTP server"""
37
-
38
- # Valid New Scale manipulator IDs
39
- VALID_MANIPULATOR_IDS = (
40
- "A",
41
- "B",
42
- "C",
43
- "D",
44
- "E",
45
- "F",
46
- "G",
47
- "H",
48
- "I",
49
- "J",
50
- "K",
51
- "L",
52
- "M",
53
- "N",
54
- "O",
55
- "P",
56
- "Q",
57
- "R",
58
- "S",
59
- "T",
60
- "U",
61
- "V",
62
- "W",
63
- "X",
64
- "Y",
65
- "Z",
66
- "AA",
67
- "AB",
68
- "AC",
69
- "AD",
70
- "AE",
71
- "AF",
72
- "AG",
73
- "AH",
74
- "AI",
75
- "AJ",
76
- "AK",
77
- "AL",
78
- "AM",
79
- "AN",
80
- )
81
-
82
- def __init__(self, port: int = 8080) -> None:
83
- """
84
- Initialize New Scale via Pathfinder handler
85
-
86
- :param port: Port of New Scale Pathfinder HTTP server
87
- :type port: int
88
- """
89
- super().__init__()
90
-
91
- self.num_axes = -1
92
- self.dimensions = Vector4(x=15, y=15, z=15, w=0)
93
-
94
- self.port = port
95
-
96
- # Test connection to New Scale HTTP server
97
- try:
98
- request.urlopen(f"http://localhost:{self.port}")
99
- except URLError:
100
- print(f"New Scale Pathfinder HTTP server not online on port {self.port}")
101
- print("Please start the HTTP server and try again.")
102
- input("Press Enter to exit...")
103
- exit(1)
104
-
105
- def query_data(self) -> dict:
106
- """Query New Scale HTTP server for data and return as dict.
107
-
108
- :return: Parsed JSON data from New Scale HTTP server.
109
- :rtype: dict
110
- """
111
- try:
112
- return json.loads(request.urlopen(f"http://localhost:{self.port}").read())
113
- except Exception as e:
114
- print(f"[ERROR]\t\t Unable to query for New Scale data: {type(e)} {e}\n")
115
-
116
- def query_manipulator_data(self, manipulator_id: str) -> dict:
117
- """Query New Scale HTTP server for data on a specific manipulator.
118
-
119
- :param manipulator_id: manipulator ID.
120
- :return: Parsed JSON data for a particular manipulator.
121
- :rtype: dict
122
- :raises ValueError: if manipulator ID is not found in query.
123
- """
124
- data_query = self.query_data()["ProbeArray"]
125
- manipulator_data = data_query[self.manipulators[manipulator_id]]
126
-
127
- # If the order of the manipulators switched (somehow)
128
- if manipulator_data["Id"] != manipulator_id:
129
- # Recalculate index and get data
130
- (manipulator_index, manipulator_data) = next(
131
- (
132
- (index, data)
133
- for index, data in enumerate(self.query_data()["ProbeArray"])
134
- if data["Id"] == manipulator_id
135
- ),
136
- (None, None),
137
- )
138
- # Update index in manipulators dict
139
- if manipulator_index:
140
- self.manipulators[manipulator_id] = manipulator_index
141
-
142
- # If data query was unsuccessful
143
- if not manipulator_data:
144
- msg = f"Unable to find manipulator {manipulator_id}"
145
- raise ValueError(msg)
146
-
147
- # Return data
148
- return manipulator_data
149
-
150
- def _get_manipulators(self) -> list:
151
- return [probe["Id"] for probe in self.query_data()["ProbeArray"]]
152
-
153
- def _register_manipulator(self, manipulator_id: str) -> None:
154
- # Check if ID is a valid New Scale manipulator ID
155
- if manipulator_id not in self.VALID_MANIPULATOR_IDS:
156
- msg = f"Invalid manipulator ID {manipulator_id}"
157
- raise ValueError(msg)
158
-
159
- # Check if ID is connected
160
- if manipulator_id not in self._get_manipulators():
161
- msg = f"Manipulator {manipulator_id} not connected"
162
- raise ValueError(msg)
163
-
164
- # Get index of the manipulator
165
- manipulator_index = next(
166
- (index for index, data in enumerate(self.query_data()["ProbeArray"]) if data["Id"] == manipulator_id),
167
- None,
168
- )
169
- if manipulator_index is None:
170
- msg = f"Unable to find manipulator {manipulator_id}"
171
- raise ValueError(msg)
172
- self.manipulators[manipulator_id] = manipulator_index
173
-
174
- def _unregister_manipulator(self, manipulator_id: str) -> None:
175
- del self.manipulators[manipulator_id]
176
-
177
- def _get_pos(self, manipulator_id: str) -> PositionalResponse:
178
- """Get the current position of the manipulator in mm
179
-
180
- :param manipulator_id: manipulator ID
181
- :return: Callback parameters (position in (x, y, z, w) (or an empty array on
182
- error) in mm, error message)
183
- """
184
- manipulator_data = self.query_manipulator_data(manipulator_id)
185
-
186
- return PositionalResponse(
187
- position=Vector4(
188
- x=manipulator_data["Tip_X_ML"], y=manipulator_data["Tip_Y_AP"], z=manipulator_data["Tip_Z_DV"], w=0
189
- )
190
- )
191
-
192
- def _get_angles(self, manipulator_id: str) -> AngularResponse:
193
- manipulator_data = self.query_manipulator_data(manipulator_id)
194
-
195
- # Apply PosteriorAngle to Polar to get the correct angle.
196
- adjusted_polar = manipulator_data["Polar"] - self.query_data()["PosteriorAngle"]
197
-
198
- return AngularResponse(
199
- angles=Vector3(
200
- x=adjusted_polar if adjusted_polar > 0 else 360 + adjusted_polar,
201
- y=manipulator_data["Pitch"],
202
- z=manipulator_data.get("ShankOrientation", 0),
203
- )
204
- )
205
-
206
- def _get_shank_count(self, manipulator_id: str) -> ShankCountResponse:
207
- for probe in self.query_data()["ProbeArray"]:
208
- if probe["Id"] == manipulator_id:
209
- return ShankCountResponse(shank_count=probe.get("ShankCount", 1))
210
-
211
- return ShankCountResponse(error="Unable to find manipulator")
212
-
213
- async def _goto_pos(self, _: GotoPositionRequest) -> PositionalResponse:
214
- raise NotImplementedError
215
-
216
- async def _drive_to_depth(self, _: DriveToDepthRequest) -> DriveToDepthResponse:
217
- raise NotImplementedError
218
-
219
- def _set_inside_brain(self, _: InsideBrainRequest) -> BooleanStateResponse:
220
- raise NotImplementedError
221
-
222
- async def _calibrate(self, manipulator_id: str, sio: socketio.AsyncServer) -> str:
223
- raise NotImplementedError
224
-
225
- def _bypass_calibration(self, manipulator_id: str) -> str:
226
- return ""
227
-
228
- def _set_can_write(self, _: CanWriteRequest) -> BooleanStateResponse:
229
- raise NotImplementedError
230
-
231
- def _unified_space_to_platform_space(self, unified_position: list[float]) -> list[float]:
232
- raise NotImplementedError
233
-
234
- def _platform_space_to_unified_space(self, platform_position: list[float]) -> list[float]:
235
- raise NotImplementedError
@@ -1,151 +0,0 @@
1
- """Handle communications with Sensapex uMp API
2
-
3
- This supports the uMp-4 manipulator. Any Sensapex variants should extend this class.
4
-
5
- Implements Sensapex uMp specific API calls including coordinating the usage of the
6
- :class:`ephys_link.platforms.sensapex_manipulator.SensapexManipulator` class.
7
-
8
- This is a subclass of :class:`ephys_link.platform_handler.PlatformHandler`.
9
- """
10
-
11
- from __future__ import annotations
12
-
13
- from pathlib import Path
14
- from typing import TYPE_CHECKING
15
-
16
- from sensapex import UMP, UMError
17
- from vbl_aquarium.models.ephys_link import (
18
- AngularResponse,
19
- BooleanStateResponse,
20
- CanWriteRequest,
21
- DriveToDepthRequest,
22
- DriveToDepthResponse,
23
- GotoPositionRequest,
24
- InsideBrainRequest,
25
- PositionalResponse,
26
- ShankCountResponse,
27
- )
28
- from vbl_aquarium.models.unity import Vector4
29
-
30
- import ephys_link.common as com
31
- from ephys_link.platform_handler import PlatformHandler
32
- from ephys_link.platforms.sensapex_manipulator import SensapexManipulator
33
-
34
- if TYPE_CHECKING:
35
- import socketio
36
-
37
-
38
- class SensapexHandler(PlatformHandler):
39
- """Handler for Sensapex platform."""
40
-
41
- def __init__(self) -> None:
42
- super().__init__()
43
-
44
- # Establish connection to Sensapex API (exit if connection fails)
45
- UMP.set_library_path(str(Path(__file__).parent.parent.absolute()) + "/resources/")
46
- self.ump = UMP.get_ump()
47
- if self.ump is None:
48
- msg = "Unable to connect to uMp"
49
- raise ValueError(msg)
50
-
51
- def _get_manipulators(self) -> list:
52
- return list(map(str, self.ump.list_devices()))
53
-
54
- def _register_manipulator(self, manipulator_id: str) -> None:
55
- if not manipulator_id.isnumeric():
56
- msg = "Manipulator ID must be numeric"
57
- raise ValueError(msg)
58
-
59
- self.manipulators[manipulator_id] = SensapexManipulator(self.ump.get_device(int(manipulator_id)))
60
-
61
- def _unregister_manipulator(self, manipulator_id: str) -> None:
62
- del self.manipulators[manipulator_id]
63
-
64
- def _get_pos(self, manipulator_id: str) -> PositionalResponse:
65
- return self.manipulators[manipulator_id].get_pos()
66
-
67
- def _get_angles(self, manipulator_id: str) -> AngularResponse:
68
- raise NotImplementedError
69
-
70
- def _get_shank_count(self, manipulator_id: str) -> ShankCountResponse:
71
- raise NotImplementedError
72
-
73
- async def _goto_pos(self, request: GotoPositionRequest) -> PositionalResponse:
74
- return await self.manipulators[request.manipulator_id].goto_pos(request)
75
-
76
- async def _drive_to_depth(self, request: DriveToDepthRequest) -> DriveToDepthResponse:
77
- return await self.manipulators[request.manipulator_id].drive_to_depth(request)
78
-
79
- def _set_inside_brain(self, request: InsideBrainRequest) -> BooleanStateResponse:
80
- self.manipulators[request.manipulator_id].set_inside_brain(request.inside)
81
- com.dprint(f"[SUCCESS]\t Set inside brain state for manipulator: {request.manipulator_id}\n")
82
- return BooleanStateResponse(state=request.inside)
83
-
84
- async def _calibrate(self, manipulator_id: str, sio: socketio.AsyncServer) -> str:
85
- try:
86
- # Move manipulator to max position
87
- await self.manipulators[manipulator_id].goto_pos([20000, 20000, 20000, 20000], 2000)
88
-
89
- # Call calibrate
90
- self.manipulators[manipulator_id].call_calibrate()
91
-
92
- # Wait for calibration to complete
93
- still_working = True
94
- while still_working:
95
- cur_pos = self.manipulators[manipulator_id].get_pos()["position"]
96
-
97
- # Check difference between current and target position
98
- for prev, cur in zip([10000, 10000, 10000, 10000], cur_pos):
99
- if abs(prev - cur) > 1:
100
- still_working = True
101
- break
102
- still_working = False
103
-
104
- # Sleep for a bit
105
- await sio.sleep(0.5)
106
-
107
- # Calibration complete
108
- self.manipulators[manipulator_id].set_calibrated()
109
- com.dprint(f"[SUCCESS]\t Calibrated manipulator {manipulator_id}\n")
110
- except UMError as e:
111
- # SDK call error
112
- print(f"[ERROR]\t\t Calling calibrate manipulator {manipulator_id}")
113
- print(f"{e}\n")
114
- return "Error calling calibrate"
115
- else:
116
- return ""
117
-
118
- def _bypass_calibration(self, manipulator_id: str) -> str:
119
- self.manipulators[manipulator_id].set_calibrated()
120
- com.dprint(f"[SUCCESS]\t Bypassed calibration for manipulator" f" {manipulator_id}\n")
121
- return ""
122
-
123
- def _set_can_write(self, request: CanWriteRequest) -> BooleanStateResponse:
124
- self.manipulators[request.manipulator_id].set_can_write(request)
125
- com.dprint(f"[SUCCESS]\t Set can_write state for manipulator {request.manipulator_id}\n")
126
- return BooleanStateResponse(state=request.can_write)
127
-
128
- def _platform_space_to_unified_space(self, platform_position: Vector4) -> Vector4:
129
- # unified <- platform
130
- # +x <- +y
131
- # +y <- -z
132
- # +z <- +x
133
- # +d <- +d
134
-
135
- return Vector4(
136
- x=platform_position.y,
137
- y=self.dimensions.z - platform_position.z,
138
- z=platform_position.x,
139
- w=platform_position.w,
140
- )
141
-
142
- def _unified_space_to_platform_space(self, unified_position: Vector4) -> Vector4:
143
- # platform <- unified
144
- # +x <- +z
145
- # +y <- +x
146
- # +z <- -y
147
- # +d <- +d
148
-
149
- return Vector4(
150
- x=unified_position.z, y=unified_position.x, z=self.dimensions.z - unified_position.y, w=unified_position.w
151
- )