diff --git a/pylabrobot/brooks/precise_flex/__init__.py b/pylabrobot/brooks/precise_flex/__init__.py new file mode 100644 index 00000000000..1ae738031a7 --- /dev/null +++ b/pylabrobot/brooks/precise_flex/__init__.py @@ -0,0 +1,55 @@ +"""Brooks PreciseFlex robots. + +Why one package for the family - every PreciseFlex arm runs the same Guidance/TCS controller and +speaks the same GPL command protocol, DataIDs, and error codes, so they share the bulk of this +driver. They differ only in kinematics (per geometry, e.g. the c10's R-P-R-R joint order, the c8A's +six axes) and gripper, which are handled by per-model device classes and per-geometry kinematics +modules within the package. Grouping by the shared controller keeps that common driver in one place +rather than duplicated per arm model. + +Scope - the PreciseFlex robot line. Implemented: + +- PreciseFlex 400 (PF400) +- PreciseFlex 3400 (PF3400) + +To be added here: + +- PreciseFlex 100 / 1400 (PF100 / PF1400) +- c-series: c3, c5, c8A, c10 +- direct-drive: DD4, DD6 +- linear rail + +Everything here is PreciseFlex-specific, including the TCS controller protocol (``tcs_modules``), +``error_codes``, and the controller DataIDs (``data_ids``) - the PreciseFlex line is the only user of +the Guidance/TCS controller, so they live with it. A future, genuinely different Brooks device family +would get its own sibling package under ``brooks/``, and anything shared would be lifted up then. + +Re-exports the public classes so ``from pylabrobot.brooks.precise_flex import PreciseFlex400`` keeps +working. +""" + +from pylabrobot.brooks.precise_flex.precise_flex import ( + Axis, + PreciseFlex400, + PreciseFlex400Backend, + PreciseFlex3400Backend, + PreciseFlexArmBackend, + PreciseFlexCartesianPose, + PreciseFlexConfiguration, + PreciseFlexDriver, + PreciseFlexError, + WorkingVolume, +) + +__all__ = [ + "Axis", + "PreciseFlex400", + "PreciseFlex400Backend", + "PreciseFlex3400Backend", + "PreciseFlexArmBackend", + "PreciseFlexCartesianPose", + "PreciseFlexConfiguration", + "PreciseFlexDriver", + "PreciseFlexError", + "WorkingVolume", +] diff --git a/pylabrobot/brooks/confirmed_firmware_versions.py b/pylabrobot/brooks/precise_flex/confirmed_firmware_versions.py similarity index 100% rename from pylabrobot/brooks/confirmed_firmware_versions.py rename to pylabrobot/brooks/precise_flex/confirmed_firmware_versions.py diff --git a/pylabrobot/brooks/data_ids.py b/pylabrobot/brooks/precise_flex/data_ids.py similarity index 100% rename from pylabrobot/brooks/data_ids.py rename to pylabrobot/brooks/precise_flex/data_ids.py diff --git a/pylabrobot/brooks/error_codes.py b/pylabrobot/brooks/precise_flex/errors.py similarity index 99% rename from pylabrobot/brooks/error_codes.py rename to pylabrobot/brooks/precise_flex/errors.py index c254e5418c9..f9d246f140c 100644 --- a/pylabrobot/brooks/error_codes.py +++ b/pylabrobot/brooks/precise_flex/errors.py @@ -1919,3 +1919,15 @@ "description": "A remote request to load a new vision project has failed because the current project has not been saved. Save the current project before attempting to load a new one.", }, } + + +class PreciseFlexError(Exception): + def __init__(self, replycode: int, message: str): + self.replycode = replycode + self.message = message + if replycode in ERROR_CODES: + text = ERROR_CODES[replycode]["text"] + description = ERROR_CODES[replycode]["description"] + super().__init__(f"PreciseFlexError {replycode}: {text}. {description} - {message}") + else: + super().__init__(f"PreciseFlexError {replycode}: {message}") diff --git a/pylabrobot/brooks/kinematics.py b/pylabrobot/brooks/precise_flex/kinematics.py similarity index 97% rename from pylabrobot/brooks/kinematics.py rename to pylabrobot/brooks/precise_flex/kinematics.py index 77590ef71ab..5df2fd612dd 100644 --- a/pylabrobot/brooks/kinematics.py +++ b/pylabrobot/brooks/precise_flex/kinematics.py @@ -22,7 +22,7 @@ from pylabrobot.capabilities.arms.standard import JointPose if TYPE_CHECKING: - from pylabrobot.brooks.precise_flex import PreciseFlexCartesianPose + from pylabrobot.brooks.precise_flex.precise_flex import PreciseFlexCartesianPose # Known PF400 link-length configs (l1 = shoulder->elbow, l2 = elbow->wrist), in mm, per the 615287 @@ -78,7 +78,7 @@ def fk(joints: JointPose, p: PF400Params) -> "PreciseFlexCartesianPose": orientation/wrist derived from the joint configuration (J3 sign and wrapped J4 sign, respectively). """ - from pylabrobot.brooks.precise_flex import PreciseFlexCartesianPose + from pylabrobot.brooks.precise_flex.precise_flex import PreciseFlexCartesianPose from pylabrobot.resources import Coordinate, Rotation j1 = joints[1] diff --git a/pylabrobot/brooks/pf400_test.ipynb b/pylabrobot/brooks/precise_flex/pf400_test.ipynb similarity index 100% rename from pylabrobot/brooks/pf400_test.ipynb rename to pylabrobot/brooks/precise_flex/pf400_test.ipynb diff --git a/pylabrobot/brooks/precise_flex.py b/pylabrobot/brooks/precise_flex/precise_flex.py similarity index 99% rename from pylabrobot/brooks/precise_flex.py rename to pylabrobot/brooks/precise_flex/precise_flex.py index b534f5d6c0b..54bf57c1704 100644 --- a/pylabrobot/brooks/precise_flex.py +++ b/pylabrobot/brooks/precise_flex/precise_flex.py @@ -7,16 +7,6 @@ from enum import IntEnum from typing import Dict, List, Literal, Optional -from pylabrobot.brooks import kinematics -from pylabrobot.brooks.confirmed_firmware_versions import ( - SUPPORTED_ROBOT_TYPES, - is_confirmed, - is_supported_model, - suggest_entry, -) -from pylabrobot.brooks.data_ids import DataID -from pylabrobot.brooks.error_codes import ERROR_CODES -from pylabrobot.brooks.tcs_modules import missing_required_modules from pylabrobot.capabilities.arms.backend import ( CanFreedrive, HasJoints, @@ -30,6 +20,18 @@ from pylabrobot.resources import Coordinate, Rotation from pylabrobot.resources.resource import Resource +# PreciseFlex-specific siblings - relative imports. +from . import kinematics +from .confirmed_firmware_versions import ( + SUPPORTED_ROBOT_TYPES, + is_confirmed, + is_supported_model, + suggest_entry, +) +from .data_ids import DataID +from .errors import PreciseFlexError +from .tcs_modules import missing_required_modules + logger = logging.getLogger(__name__) @@ -157,23 +159,6 @@ def working_volume(self) -> WorkingVolume: return WorkingVolume(inner=inner, outer=outer, zmin=zmin, zmax=zmax) -# --------------------------------------------------------------------------- -# Exceptions -# --------------------------------------------------------------------------- - - -class PreciseFlexError(Exception): - def __init__(self, replycode: int, message: str): - self.replycode = replycode - self.message = message - if replycode in ERROR_CODES: - text = ERROR_CODES[replycode]["text"] - description = ERROR_CODES[replycode]["description"] - super().__init__(f"PreciseFlexError {replycode}: {text}. {description} - {message}") - else: - super().__init__(f"PreciseFlexError {replycode}: {message}") - - # --------------------------------------------------------------------------- # Driver — owns Socket I/O and device lifecycle # --------------------------------------------------------------------------- diff --git a/pylabrobot/brooks/precise_flex_tests.py b/pylabrobot/brooks/precise_flex/precise_flex_tests.py similarity index 99% rename from pylabrobot/brooks/precise_flex_tests.py rename to pylabrobot/brooks/precise_flex/precise_flex_tests.py index 5c8c80c3207..9c7934e1628 100644 --- a/pylabrobot/brooks/precise_flex_tests.py +++ b/pylabrobot/brooks/precise_flex/precise_flex_tests.py @@ -2,8 +2,7 @@ from typing import Tuple from unittest.mock import AsyncMock, MagicMock -from pylabrobot.brooks import kinematics -from pylabrobot.brooks.precise_flex import Axis, PreciseFlex400Backend +from pylabrobot.brooks.precise_flex import Axis, PreciseFlex400Backend, kinematics def _make_backend( diff --git a/pylabrobot/brooks/tcs_modules.py b/pylabrobot/brooks/precise_flex/tcs_modules.py similarity index 100% rename from pylabrobot/brooks/tcs_modules.py rename to pylabrobot/brooks/precise_flex/tcs_modules.py