Skip to content

Commit 588abbb

Browse files
rickwierengaclaude
andcommitted
Safety, correctness, and architecture improvements across v1b1
Safety & correctness: - Unify ChannelizedError: legacy re-exports from capabilities (fixes silent per-channel recovery failure when legacy backends raise through adapters) - Add @need_capability_ready to 35 methods across 11 capabilities - Make Device.stop() and Machine.stop() idempotent (safe double-call) - Fix GripperArm state timing: validate before hardware, update after success - Make USB.stop() idempotent - Fix Head96 center() bug: use get_absolute_location(x="c", y="c") instead of + center() to handle rotated plates correctly (was 9mm off) - Add Plate.plate_type and Trough.bottom_type/through_base_to_container_base to serialization Head96 backend (19 new methods): - Add initialize, move_to_z_safety, park, move_to_coordinate, move_y, move_z - Add request_position, request_tip_presence, request_tadm_status - Add dispensing_drive methods, firmware version query - Proper _on_setup (check init, park) and _on_stop (z-safety, park) - Legacy delegates to new backend (14 methods converted) iSWAP forwarding: - park_iswap, iswap_open_gripper, iswap_close_gripper now delegate to self._iswap.* with proper state tracking Autoload lifecycle: - _on_setup checks init status before reinitializing, parks after - _on_stop parks to safe position - Legacy request_pump_settings delegates to wash_station Architecture: - Move arms/ to capabilities/arms/ with deprecation shims - Move liquid classes: HamiltonLiquidClass to hamilton/liquid_handlers/liquid_class.py, STAR classes to star/liquid_classes/, vantage to hamilton/lh/vantage/ - Create import shims for 19 old module paths with DeprecationWarning - Fix doc links: Sphinx cross-refs, relative paths, HTML links - Clean up redundant assertions in legacy Head96 delegation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 819d0e3 commit 588abbb

123 files changed

Lines changed: 2380 additions & 1399 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/contributor_guide/visualizer.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Useful entry points are the examples in the user guide or the unit tests in
5959

6060
Because communication happens over websockets, you can also drive the visualizer
6161
from unit tests or scripts without a physical robot. The
62-
{class}`~pylabrobot.liquid_handling.backends.chatterbox.ChatterboxBackend` works
62+
{class}`~pylabrobot.legacy.liquid_handling.backends.chatterbox.ChatterboxBackend` works
6363
well for this purpose.
6464

6565
## Contributing tips

docs/resources/container/container.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Container
22
=========
33

4-
Resources that contain liquid are subclasses of :class:`~pylabrobot.resources.container.Container`. This class provides a :class:`~pylabrobot.resources.volume_tracker.VolumeTracker` that helps :class:`~pylabrobot.liquid_handling.liquid_handler.LiquidHandler` keep track of the liquid in the resource. (For more information on trackers, check out :doc:`/user_guide/machine-agnostic-features/using-trackers`). Examples of subclasses of `Container` are :class:`~pylabrobot.resources.Well` and :class:`~pylabrobot.resources.trough.Trough`.
4+
Resources that contain liquid are subclasses of :class:`~pylabrobot.resources.container.Container`. This class provides a :class:`~pylabrobot.resources.volume_tracker.VolumeTracker` that helps :class:`~pylabrobot.legacy.liquid_handling.liquid_handler.LiquidHandler` keep track of the liquid in the resource. (For more information on trackers, check out :doc:`/user_guide/machine-agnostic-features/using-trackers`). Examples of subclasses of `Container` are :class:`~pylabrobot.resources.Well` and :class:`~pylabrobot.resources.trough.Trough`.
55

66
It is possible to instantiate a `Container` directly:
77

docs/resources/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,14 @@ PLR's `Resource` subclasses in the inheritance tree are:
9393

9494
<!-- ResourceHolder subtree -->
9595
<tr><td>├── <a href="resource-holder/resource-holder.html">ResourceHolder</a></td></tr>
96-
<tr><td>│ └── <a href="resource-holder/plate-holder/plate-holder.html">PlateHolder</a></td></tr>
96+
<tr><td>│ └── <a href="resource-holder/plate-holder.html">PlateHolder</a></td></tr>
9797

9898
<!-- Others -->
9999
<tr><td>├── Lid</td></tr>
100100
<tr><td>├── <a href="plate-adapter/plate-adapter.html">PlateAdapter</a></td></tr>
101101

102102
<tr><td>├── ResourceStack</td></tr>
103-
<tr><td>│ └── <a href="resource-holder/plate-holder/plate-holder.html">NestedTipRackStack (to be made)</a></td></tr>
103+
<tr><td>│ └── <a href="resource-holder/plate-holder.html">NestedTipRackStack (to be made)</a></td></tr>
104104

105105
<tr><td>└── Workcell (to be made)</td></tr>
106106
</table>

docs/resources/introduction.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ While you can instantiate a `Resource` directly, several subclasses of methods e
88

99
The relation between resources is modelled by a tree, specifically an [_arborescence_](<https://en.wikipedia.org/wiki/Arborescence_(graph_theory)>) (a directed, rooted tree). The location of a resource in the tree is a Cartesian coordinate and always relative to the bottom front left corner of its immediate parent. The absolute location, the location of the resource wrt the root of the tree it is in, can be computed using {meth}`~pylabrobot.resources.resource.Resource.get_absolute_location`. The location wrt any resource between a given one and the root can be computed using {meth}`~pylabrobot.resources.resource.Resource.get_location_wrt`. The x-axis is left (smaller) and right (larger); the y-axis is front (small) and back (larger); the z-axis is down (smaller) and up (higher). Each resource has `children` and `parent` attributes that allow you to navigate the tree.
1010

11-
{class}`pylabrobot.machines.machine.Machine` is a special type of resource that represents a physical machine, such as a liquid handling robot ({class}`pylabrobot.liquid_handling.liquid_handler.LiquidHandler`) or a plate reader ({class}`pylabrobot.plate_reading.plate_reader.PlateReader`). Machines have a `backend` attribute linking to the backend that is responsible for converting PyLabRobot commands into commands that a specific machine can understand. Other than that, Machines, including {class}`pylabrobot.liquid_handling.liquid_handler.LiquidHandler`, are just like any other Resource.
11+
{class}`pylabrobot.machines.machine.Machine` is a special type of resource that represents a physical machine, such as a liquid handling robot ({class}`pylabrobot.legacy.liquid_handling.liquid_handler.LiquidHandler`) or a plate reader ({class}`pylabrobot.legacy.plate_reading.plate_reader.PlateReader`). Machines have a `backend` attribute linking to the backend that is responsible for converting PyLabRobot commands into commands that a specific machine can understand. Other than that, Machines, including {class}`pylabrobot.legacy.liquid_handling.liquid_handler.LiquidHandler`, are just like any other Resource.
1212

1313
## Defining a simple resource
1414

docs/user_guide/01_material-handling/temperature-controllers/temperature-controllers.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ Implementation
5858
Backend
5959
^^^^^^^
6060

61-
PyLabRobot programmatically defines Temperature Controller machines based on the :class:`~pylabrobot.temperature_controlling.temperature_controller.TemperatureController` base class.
61+
PyLabRobot programmatically defines Temperature Controller machines based on the :class:`~pylabrobot.legacy.temperature_controlling.temperature_controller.TemperatureController` base class.
6262

6363
e.g.:
6464

6565
.. code-block:: python
6666
67-
from pylabrobot.temperature_controlling.temperature_controller import (
67+
from pylabrobot.legacy.temperature_controlling.temperature_controller import (
6868
TemperatureControllerBackend
6969
)
7070

docs/user_guide/brooks/precise_flex/hello-world.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
{
3030
"cell_type": "markdown",
3131
"id": "hmybpz3v5bk",
32-
"source": "## Arm capabilities\n\nThe PreciseFlex exposes an {class}`~pylabrobot.arms.orientable_arm.OrientableArm` on `pf.arm`. For the full arm API, see [Arms](../../capabilities/arms).",
32+
"source": "## Arm capabilities\n\nThe PreciseFlex exposes an {class}`~pylabrobot.capabilities.arms.orientable_arm.OrientableArm` on `pf.arm`. For the full arm API, see [Arms](../../capabilities/arms).",
3333
"metadata": {}
3434
},
3535
{

docs/user_guide/capabilities/arms.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
Arms are capabilities for picking up, moving, and placing labware (plates, lids, etc.) on the deck. PLR provides two arm types:
44

5-
- {class}`~pylabrobot.arms.arm.GripperArm` -- a fixed-axis gripper arm (e.g. Hamilton core grippers). Grips along a single axis.
6-
- {class}`~pylabrobot.arms.orientable_arm.OrientableArm` -- a rotatable gripper arm (e.g. Hamilton iSWAP). Can grip from any direction.
5+
- {class}`~pylabrobot.capabilities.arms.arm.GripperArm` -- a fixed-axis gripper arm (e.g. Hamilton core grippers). Grips along a single axis.
6+
- {class}`~pylabrobot.capabilities.arms.orientable_arm.OrientableArm` -- a rotatable gripper arm (e.g. Hamilton iSWAP). Can grip from any direction.
77

88
Both inherit from `_BaseArm`, which is a {class}`~pylabrobot.capabilities.capability.Capability`.
99

@@ -53,7 +53,7 @@ await lh.iswap.move_resource(
5353
### OrientableArm: grip direction
5454

5555
```python
56-
from pylabrobot.arms.standard import GripDirection
56+
from pylabrobot.capabilities.arms.standard import GripDirection
5757

5858
await lh.iswap.pick_up_resource(plate, direction=GripDirection.LEFT)
5959
await lh.iswap.drop_resource(reader, direction=GripDirection.FRONT)
@@ -64,7 +64,7 @@ await lh.iswap.drop_resource(reader, direction=GripDirection.FRONT)
6464
- **Coordinates are in the reference resource's frame** (typically the deck). The arm computes gripper target coordinates from the resource's position, dimensions, and the destination type.
6565
- **`pickup_distance_from_top`** controls how far down from the top face the gripper grips. If `None`, the resource's `preferred_pickup_location` is used, or a default of 5 mm.
6666
- **Resource tree is updated automatically.** After a successful `drop_resource`, the resource is unassigned from its old parent and assigned to the destination.
67-
- **`GripOrientation`** is either a {class}`~pylabrobot.arms.standard.GripDirection` enum (`FRONT`, `RIGHT`, `BACK`, `LEFT`) or a float in degrees.
67+
- **`GripOrientation`** is either a {class}`~pylabrobot.capabilities.arms.standard.GripDirection` enum (`FRONT`, `RIGHT`, `BACK`, `LEFT`) or a float in degrees.
6868
- **`request_gripper_location()`** queries the hardware for the current end effector position. `get_picked_up_resource()` returns the internally tracked state (no hardware call).
6969

7070
## Supported hardware
@@ -74,4 +74,4 @@ await lh.iswap.drop_resource(reader, direction=GripDirection.FRONT)
7474

7575
## API reference
7676

77-
See {class}`~pylabrobot.arms.arm.GripperArm`, {class}`~pylabrobot.arms.orientable_arm.OrientableArm`, {class}`~pylabrobot.arms.backend.GripperArmBackend`, and {class}`~pylabrobot.arms.backend.OrientableGripperArmBackend`.
77+
See {class}`~pylabrobot.capabilities.arms.arm.GripperArm`, {class}`~pylabrobot.capabilities.arms.orientable_arm.OrientableArm`, {class}`~pylabrobot.capabilities.arms.backend.GripperArmBackend`, and {class}`~pylabrobot.capabilities.arms.backend.OrientableGripperArmBackend`.

docs/user_guide/capabilities/dispensing/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ Peristaltic dispensers push fluid through flexible tubing using a rotating pump
3535

3636
| Device | Manufacturer | Peristaltic | Syringe |
3737
|--------|-------------|:-----------:|:-------:|
38-
| [Multidrop Combi](../thermo_fisher/multidrop_combi/hello-world) | Thermo Fisher | yes | -- |
39-
| [EL406](../agilent/biotek/el406/hello-world) | BioTek (Agilent) | yes | yes |
38+
| [Multidrop Combi](../../thermo_fisher/multidrop_combi/hello-world) | Thermo Fisher | yes | -- |
39+
| [EL406](../../agilent/biotek/el406/hello-world) | BioTek (Agilent) | yes | yes |
4040

4141
```{toctree}
4242
:maxdepth: 1

docs/user_guide/hamilton/star/core-grippers.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@
533533
},
534534
{
535535
"cell_type": "markdown",
536-
"source": "## Moving resources\n\nUse {meth}`~pylabrobot.hamilton.liquid_handlers.star.star.STAR.core_grippers` as an async context manager. It picks up the gripper tools when entering and returns them when exiting. The yielded {class}`~pylabrobot.arms.arm.GripperArm` has two APIs:\n\n- `move_resource`: a single call that picks up a resource and drops it at a destination.\n- `pick_up_resource` and `drop_resource`: two separate calls for more control over timing.",
536+
"source": "## Moving resources\n\nUse {meth}`~pylabrobot.hamilton.liquid_handlers.star.star.STAR.core_grippers` as an async context manager. It picks up the gripper tools when entering and returns them when exiting. The yielded {class}`~pylabrobot.capabilities.arms.arm.GripperArm` has two APIs:\n\n- `move_resource`: a single call that picks up a resource and drops it at a destination.\n- `pick_up_resource` and `drop_resource`: two separate calls for more control over timing.",
537537
"metadata": {}
538538
},
539539
{

docs/user_guide/hamilton/star/iswap.ipynb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"source": [
77
"# iSWAP Module\n",
88
"\n",
9-
"The iSWAP is a plate transport gripper arm on the Hamilton STAR(let). After {meth}`~pylabrobot.hamilton.liquid_handlers.star.star.STAR.setup`, it is available as `star.iswap` — an {class}`~pylabrobot.arms.arm.OrientableArm` whose backend is an {class}`~pylabrobot.hamilton.liquid_handlers.star.iswap.iSWAPBackend`.\n",
9+
"The iSWAP is a plate transport gripper arm on the Hamilton STAR(let). After {meth}`~pylabrobot.hamilton.liquid_handlers.star.star.STAR.setup`, it is available as `star.iswap` — an {class}`~pylabrobot.capabilities.arms.arm.OrientableArm` whose backend is an {class}`~pylabrobot.hamilton.liquid_handlers.star.iswap.iSWAPBackend`.\n",
1010
"\n",
1111
"This notebook covers low-level iSWAP control: parking, gripper, rotations, slow movement, and manual teaching/calibration. For high-level plate movement, use `star.iswap.move_resource(plate, destination)`."
1212
]
@@ -36,7 +36,7 @@
3636
"cell_type": "markdown",
3737
"metadata": {},
3838
"source": [
39-
"After `setup()`, `star.iswap` is an {class}`~pylabrobot.arms.arm.OrientableArm` if the hardware is installed, or `None` if it is not. The low-level backend is `star.iswap.backend`, which is an {class}`~pylabrobot.hamilton.liquid_handlers.star.iswap.iSWAPBackend`."
39+
"After `setup()`, `star.iswap` is an {class}`~pylabrobot.capabilities.arms.arm.OrientableArm` if the hardware is installed, or `None` if it is not. The low-level backend is `star.iswap.backend`, which is an {class}`~pylabrobot.hamilton.liquid_handlers.star.iswap.iSWAPBackend`."
4040
]
4141
},
4242
{
@@ -62,7 +62,7 @@
6262
{
6363
"cell_type": "markdown",
6464
"metadata": {},
65-
"source": "### Opening the gripper\n\nOpen the iSWAP gripper using {meth}`~pylabrobot.arms.orientable_arm.OrientableArm.open_gripper`. **Warning**: this will release any object that is gripped. Useful for error recovery."
65+
"source": "### Opening the gripper\n\nOpen the iSWAP gripper using {meth}`~pylabrobot.capabilities.arms.orientable_arm.OrientableArm.open_gripper`. **Warning**: this will release any object that is gripped. Useful for error recovery."
6666
},
6767
{
6868
"cell_type": "code",
@@ -223,7 +223,7 @@
223223
{
224224
"cell_type": "markdown",
225225
"metadata": {},
226-
"source": "### Closing the gripper\n\nClose the iSWAP gripper using {meth}`~pylabrobot.arms.orientable_arm.OrientableArm.close_gripper`. This will throw an error if there is no object to grip. You can optionally pass {class}`~pylabrobot.hamilton.liquid_handlers.star.iswap.iSWAPBackend.CloseGripperParams` to control grip strength and plate width tolerance."
226+
"source": "### Closing the gripper\n\nClose the iSWAP gripper using {meth}`~pylabrobot.capabilities.arms.orientable_arm.OrientableArm.close_gripper`. This will throw an error if there is no object to grip. You can optionally pass {class}`~pylabrobot.hamilton.liquid_handlers.star.iswap.iSWAPBackend.CloseGripperParams` to control grip strength and plate width tolerance."
227227
},
228228
{
229229
"cell_type": "code",
@@ -293,7 +293,7 @@
293293
"\n",
294294
"![iSWAP positions](img/iswap_positions.png)\n",
295295
"\n",
296-
"The `rotate` method moves the wrist drive and the rotation drive simultaneously in one smooth motion. It takes a parameter for the rotation drive ({class}`~pylabrobot.hamilton.liquid_handlers.star.iswap.iSWAPBackend.RotationDriveOrientation`), and the final `grip_direction` ({class}`~pylabrobot.arms.standard.GripDirection`) of the iSWAP. The `grip_direction` is the same parameter used by high-level plate movement and is with respect to the deck. This is easier than controlling the rotation drive, which is with respect to the wrist drive.\n",
296+
"The `rotate` method moves the wrist drive and the rotation drive simultaneously in one smooth motion. It takes a parameter for the rotation drive ({class}`~pylabrobot.hamilton.liquid_handlers.star.iswap.iSWAPBackend.RotationDriveOrientation`), and the final `grip_direction` ({class}`~pylabrobot.capabilities.arms.standard.GripDirection`) of the iSWAP. The `grip_direction` is the same parameter used by high-level plate movement and is with respect to the deck. This is easier than controlling the rotation drive, which is with respect to the wrist drive.\n",
297297
"\n",
298298
"For example, to extend the iSWAP fully to the left, call `rotate(rotation_drive=iSWAPBackend.RotationDriveOrientation.LEFT, grip_direction=GripDirection.RIGHT)`. `GripDirection.RIGHT` means the gripper will be gripping the object from the right hand side, so the gripper fingers point left with respect to the deck. Compare this to `rotate(rotation_drive=iSWAPBackend.RotationDriveOrientation.RIGHT, grip_direction=GripDirection.RIGHT)`, where the gripper fingers still point left, but the rotation drive points right and the wrist drive is in \"REVERSE\" orientation (folded up).\n",
299299
"\n",
@@ -307,7 +307,7 @@
307307
"outputs": [],
308308
"source": [
309309
"from pylabrobot.hamilton.liquid_handlers.star.iswap import iSWAPBackend\n",
310-
"from pylabrobot.arms.standard import GripDirection\n",
310+
"from pylabrobot.capabilities.arms.standard import GripDirection\n",
311311
"\n",
312312
"await star.iswap.backend.rotate(\n",
313313
" rotation_drive=iSWAPBackend.RotationDriveOrientation.LEFT,\n",

0 commit comments

Comments
 (0)