Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
a34e085
kx2
rickwierenga Feb 7, 2026
18272cc
tutorial update, connection update
BioCam Feb 8, 2026
7cf61d3
Merge branch 'main' into kx2-backend
BioCam Feb 8, 2026
f3fadad
change self.calculate_move_abs_all_axes
BioCam Feb 8, 2026
32d0390
try process message; check if future already done
rickwierenga Feb 8, 2026
290ad1e
remove scila.ipynb accidentally modified in kx2 PR
rickwierenga Apr 14, 2026
74721cb
Merge remote-tracking branch 'origin/v1b1' into kx2-backend
rickwierenga Apr 14, 2026
046af46
port KX2 to new arm architecture under paa/
rickwierenga Apr 14, 2026
788d4c8
replace prints with module logger in KX2
rickwierenga Apr 14, 2026
3029e9f
scaffold KX2CanopenDriver (parallel class, not yet wired up)
rickwierenga Apr 14, 2026
f93dc6c
implement SDO on KX2CanopenDriver
rickwierenga Apr 14, 2026
95fb03e
implement binary_interpreter on KX2CanopenDriver
rickwierenga Apr 14, 2026
9344217
implement motor primitives, DS402 controlword, PVT mode on KX2Canopen…
rickwierenga Apr 14, 2026
0845a02
implement connect_part_two + PDO mapping on KX2CanopenDriver
rickwierenga Apr 14, 2026
6095dec
implement os_interpreter + homing on KX2CanopenDriver
rickwierenga Apr 14, 2026
2e1084a
expose KX2Canopen device alongside legacy KX2
rickwierenga Apr 14, 2026
ec44596
raise canopen SDO timeout to 1s
rickwierenga Apr 14, 2026
c6da02b
flip hello-world to KX2Canopen for hardware testing
rickwierenga Apr 14, 2026
1a0c2ef
stop treating OS command status=1 as an error in os_interpreter
rickwierenga Apr 14, 2026
200065b
switch KX2 over to canopen driver, delete legacy transport
rickwierenga Apr 14, 2026
6290d1e
fix freedrive re-enable + notebook blockers
rickwierenga Apr 14, 2026
c67bf61
privatize driver methods that aren't part of the user API
rickwierenga Apr 14, 2026
18dd605
privatize interpreters + I/O on driver
rickwierenga Apr 14, 2026
8ee428c
move motor_send_command + get_estop_state to KX2Driver
rickwierenga Apr 14, 2026
62f4f5f
un-privatize driver methods consumed by KX2ArmBackend
rickwierenga Apr 14, 2026
08e5e7b
add KX2BarcodeReader for the onboard Microscan-style serial reader
rickwierenga Apr 15, 2026
73bd30b
send CR+LF (not just CR) to KX2 barcode reader + expand notebook section
rickwierenga Apr 21, 2026
38718a3
harden KX2 motor_enable + fix axis-identify comprehension
rickwierenga Apr 23, 2026
b7a5688
poll MS for move completion instead of TPDO3 MotionComplete
rickwierenga Apr 23, 2026
81cff3d
split KX2 layers: driver is pure CAN transport, backend owns robot to…
rickwierenga Apr 24, 2026
52820c3
fold gripper geometry into KX2 IK/FK
rickwierenga Apr 24, 2026
7fa2d2d
drop leading underscore on KX2Driver methods used by backend
rickwierenga Apr 24, 2026
663ce42
fix KX2 dead-error paths and untruncate grip force
rickwierenga Apr 24, 2026
3a2e586
native int/float through KX2 driver — no more stringified numbers
rickwierenga Apr 24, 2026
73e262f
revert KX2 user_program_run to return 0 on success
rickwierenga Apr 24, 2026
edefa19
restructure KX2 backend: split kinematics + config; native types end-…
rickwierenga Apr 25, 2026
2393657
KX2 barcode reader: rename + Denso MDI-4050 docs + dump_config + macO…
rickwierenga Apr 25, 2026
ea04caf
KX2 proximity sensor: read + drive-side auto-halt Z search
rickwierenga Apr 26, 2026
75fa5f9
KX2 proximity search: port C# MotorStop, fix post-halt drive recovery
rickwierenga Apr 26, 2026
1fe13da
KX2 setup: split drive_get_parameters; raise on motor_enable failure
rickwierenga Apr 26, 2026
528ecdc
KX2 motion API: physical units (mm/s, deg/s) and motion_limits()
rickwierenga Apr 27, 2026
3101f1a
productive work
rickwierenga Apr 27, 2026
7d30a73
KX2: clear CI gates + review polish
rickwierenga Apr 28, 2026
3968dc7
KX2: expose has_rail/has_servo_gripper kwargs; async-safe yeet prompt
rickwierenga Apr 28, 2026
9e3b5eb
KX2 + CanFreedrive: review polish round 3
rickwierenga Apr 28, 2026
f16707c
KX2 Axis: add is_motion / is_linear properties; drop _LINEAR_AXES
rickwierenga Apr 28, 2026
89bb466
KX2: collapse motors_move_joint kwargs into a single JointMoveParams
rickwierenga Apr 28, 2026
6a133cd
KX2: drop duplicate trajectory math (_yeet_profile_time, _yeet_wrap_t…
rickwierenga Apr 28, 2026
d038c47
KX2: split GripperConfig out of KX2Config; kinematics takes (c, t)
rickwierenga Apr 28, 2026
45968db
KX2Config.axes: Dict[Axis, AxisConfig]; drop int<->Axis round-trips
rickwierenga Apr 28, 2026
bf6be16
KX2: hide internal driver types; loop property; topology guard in driver
rickwierenga Apr 28, 2026
34e96e4
KX2 motion API: revert physical units back to vel_pct/accel_pct
rickwierenga Apr 28, 2026
6d93d02
KX2: cap gripper Cartesian speed via FD-based generic helper
rickwierenga Apr 29, 2026
e6bdadd
KX2: poll setpoint_ack in motors_move_start to fix dropped moves
rickwierenga Apr 29, 2026
d053f29
KX2: motion lock, int(axis) at driver boundary, fix params silent drop
rickwierenga Apr 30, 2026
ac0d6d1
KX2: fix public surface — rename leaked types, fix __init__ exports
rickwierenga Apr 30, 2026
2009c6a
KX2: handle CANopen EMCY frames for immediate fault surfacing
rickwierenga Apr 30, 2026
6a9cd20
KX2: surface EMCY in poll loop, auto-recover disabled drives before m…
rickwierenga Apr 30, 2026
7f2861f
KX2: collapse move_to_gripper_location into move_to_location, co-loca…
rickwierenga Apr 30, 2026
f792fb3
KX2: gripping force per-close, privatize the setter and reader
rickwierenga Apr 30, 2026
37a168f
KX2: drop wrist-sign API — wrist drive wraps freely
rickwierenga Apr 30, 2026
de8773e
KX2: motor_enable retry covers both directions
rickwierenga May 1, 2026
5bf36e6
KX2: move trajectory planner from arm_backend.py into kinematics.py
rickwierenga May 1, 2026
f9ca363
KX2: rename GripperConfig -> GripperParams to disambiguate from Servo…
rickwierenga May 1, 2026
83f129a
KX2: small polish — gather joint reads, drop int(Axis) casts, op-mode…
rickwierenga May 1, 2026
e5ab70a
KX2: planner unit tests — 38 passing + 2 xfail capturing latent bugs
rickwierenga May 1, 2026
c7b1488
KX2: fix mypy narrowing in tests
rickwierenga May 1, 2026
5640cc4
KX2: TPDO3-push setpoint-ack, SDO 16-bit index, pvt re-arm, planner c…
rickwierenga May 1, 2026
c5b2882
KX2: small polish — _send_bi race fix, asin clamp, skip-axis vel, _EP…
rickwierenga May 1, 2026
b6ca5d4
KX2: tag MF-polling and PPM-setpoint errors with Axis {nid}
rickwierenga May 1, 2026
d6dd639
KX2: pin exact sync-algorithm values in planner tests
rickwierenga May 1, 2026
f4427fb
KX2: collapse duplicate direction-aware delta + parameterize cap-help…
rickwierenga May 3, 2026
4c7f1ce
KX2: DS402 fault-reset edge-pulse via SW polling
rickwierenga May 3, 2026
5917e4f
KX2: fix elbow >90° round-trip — missing zero_offset in inverse refle…
rickwierenga May 3, 2026
c8a60b6
KX2: home routines are gripper-only — drop the axis arg, rename helpers
rickwierenga May 3, 2026
e854279
KX2: FK/IK anchor tests pinning real-value behavior
rickwierenga May 3, 2026
54c69e3
KX2: driver-side tests — _trigger_new_setpoint, _read_axis_config, fi…
rickwierenga May 3, 2026
ebd74ac
KX2: Cartesian-linear path via IPM/PVT streaming, opt-in path='linear'
rickwierenga May 3, 2026
85c7d59
KX2: wire barcode reader as an optional capability on KX2
rickwierenga May 3, 2026
bb65ad9
Merge v1b1: BarcodeScanner per-call read_time
rickwierenga May 4, 2026
8382742
KX2: drop read_time from BCR object, take it per scan() call
rickwierenga May 4, 2026
4d9f44b
Merge v1b1: BarcodeScanner returns Optional[Barcode]
rickwierenga May 4, 2026
9e60bbb
KX2 BCR: return None on read-window timeout instead of raising
rickwierenga May 4, 2026
3fef4b9
KX2: collapse EMCY state to one per-node struct; clear in find_z
rickwierenga May 4, 2026
42f6ce4
KX2: split proximity sensor docs into its own notebook
rickwierenga May 4, 2026
365ec70
KX2: find_z takes z_start/z_end instead of max_descent
rickwierenga May 4, 2026
acd491f
KX2: add find_with_proximity_sensor for generic Cartesian sweeps
rickwierenga May 4, 2026
7a495f7
KX2: split barcode reader docs into its own notebook
rickwierenga May 4, 2026
6088c5b
KX2: drop direction arg from find_with_proximity_sensor
rickwierenga May 4, 2026
bb626c4
Merge v1b1: PreciseFlex pose rename + Brooks I/O error codes
rickwierenga May 12, 2026
e0e5896
KX2: get_estop_state checks only SR bits 14/15, drop full-register match
rickwierenga May 12, 2026
f1a8093
KX2: rename GripperLocation → CartesianPose; sync docs and notebooks
rickwierenga May 12, 2026
5e2a114
KX2: fix FK/IK to model gripper hanging off wrist axis (location = gr…
rickwierenga May 13, 2026
f3bccd5
Arm: rename request_gripper_location → request_gripper_pose
rickwierenga May 13, 2026
c9ebba2
Merge v1b1: rename request_gripper_location → request_gripper_pose
rickwierenga May 13, 2026
bc71517
KX2: smooth PVT trajectories via move_parametric / move_through_waypo…
rickwierenga May 17, 2026
2719e0f
Arms: rename GripDirection→GripperDirection (string literal); rotate …
rickwierenga May 14, 2026
c1368ff
Unify gripper API: move_gripper(width, force_sensing) + class hierarc…
rickwierenga May 17, 2026
dd6434b
KX2: update park pose (SHOULDER=2, Z=750, ELBOW=1, WRIST=356)
rickwierenga May 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 0 additions & 42 deletions docs/api/pylabrobot.arms.rst

This file was deleted.

2 changes: 1 addition & 1 deletion docs/api/pylabrobot.hamilton.rst
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ STAR Liquid Handler
.. autoclass:: pylabrobot.hamilton.liquid_handlers.star.iswap.iSWAPBackend.ParkParams
:members:

.. autoclass:: pylabrobot.hamilton.liquid_handlers.star.iswap.iSWAPBackend.CloseGripperParams
.. autoclass:: pylabrobot.hamilton.liquid_handlers.star.iswap.iSWAPBackend.GripParams
:members:

.. autoclass:: pylabrobot.hamilton.liquid_handlers.star.iswap.iSWAPBackend.PickUpParams
Expand Down
32 changes: 32 additions & 0 deletions docs/api/pylabrobot.paa.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.. currentmodule:: pylabrobot.paa

pylabrobot.paa package
======================

KX2
---

.. currentmodule:: pylabrobot.paa.kx2

.. autosummary::
:toctree: _autosummary
:nosignatures:
:recursive:

KX2
KX2Driver
KX2ArmBackend
KX2BarcodeReader
KX2BarcodeReaderDriver
KX2BarcodeReaderBackend
KX2Config
Axis

.. autoclass:: pylabrobot.paa.kx2.arm_backend.KX2ArmBackend.CartesianMoveParams
:members:

.. autoclass:: pylabrobot.paa.kx2.arm_backend.KX2ArmBackend.JointMoveParams
:members:

.. autoclass:: pylabrobot.paa.kx2.arm_backend.KX2ArmBackend.GripParams
:members:
1 change: 1 addition & 0 deletions docs/api/pylabrobot.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Manufacturers
pylabrobot.mettler_toledo
pylabrobot.molecular_devices
pylabrobot.opentrons
pylabrobot.paa
pylabrobot.qinstruments
pylabrobot.tecan
pylabrobot.thermo_fisher
2 changes: 1 addition & 1 deletion docs/resources/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ PLR's `Resource` subclasses in the inheritance tree are:
<tr><td><a href="introduction.html">Resource</a></td></tr>
<!-- Arm subtree -->
<tr><td>├── <a href="deck/deck.html">Arm</a></td></tr>
<tr><td>│ ├── ArticulatedArm</td></tr>
<tr><td>│ ├── ArticulatedGripperArm</td></tr>
<tr><td>│ ├── CartesianArm</td></tr>
<tr><td>│ └── SCARA</td></tr>

Expand Down
1 change: 1 addition & 0 deletions docs/user_guide/_getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Different machines use different communication modes. Replace `[usb]` with one o
| `cytation-microscopy` | numpy (1.26), opencv-python | Cytation imager |
| `sila` | zeroconf, grpcio | SiLA devices |
| `pico` | opencv-python, numpy, sila | ImageXpress Pico microscope |
| `canopen` | canopen (pulls python-can) | CANopen devices: e.g. PAA KX2 plate handler. Also requires a vendor CAN driver: PEAK PCAN-Basic on macOS/Windows, the in-tree `peak_pci` module on Linux |
| `dev` | All of the above + testing/linting tools | Development |

Or install all dependencies:
Expand Down
58 changes: 11 additions & 47 deletions docs/user_guide/brooks/precise_flex/hello-world.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,11 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": null,
"id": "klqi0r4257k",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2026-04-20 21:20:11,081 - pylabrobot.brooks.precise_flex - INFO - [PreciseFlex 10.0.0.1] connected: port=10100\n"
]
}
],
"source": [
"from pylabrobot.brooks import PreciseFlex400\n",
"\n",
"pf = PreciseFlex400(host=\"10.0.0.1\", port=10100, has_rail=True)\n",
"await pf.setup()"
]
"outputs": [],
"source": "from pylabrobot.brooks import PreciseFlex400\n\n# closed_gripper_position is the firmware-unit value at min_gripper_width.\n# Calibrate against your specific gripper before first use.\npf = PreciseFlex400(host=\"10.0.0.1\", closed_gripper_position=500.0, port=10100, has_rail=True)\nawait pf.setup()"
},
{
"cell_type": "markdown",
Expand All @@ -74,7 +61,7 @@
"source": [
"## Arm capabilities\n",
"\n",
"The PreciseFlex exposes an {class}`~pylabrobot.capabilities.arms.orientable_arm.OrientableArm` on `pf.arm`. For the full arm API, see [Arms](../../capabilities/arms)."
"The PreciseFlex exposes an {class}`~pylabrobot.capabilities.arms.orientable_arm.OrientableGripperArm` on `pf.arm`. For the full arm API, see [Arms](../../capabilities/arms)."
]
},
{
Expand All @@ -87,34 +74,11 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": null,
"id": "trbcgxwrh6k",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2026-04-20 21:20:11,097 - pylabrobot.brooks.precise_flex - INFO - [PreciseFlex 10.0.0.1] open_gripper: width_mm=120\n",
"2026-04-20 21:20:11,233 - pylabrobot.brooks.precise_flex - INFO - [PreciseFlex 10.0.0.1] close_gripper: width_mm=80\n"
]
},
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"await pf.arm.open_gripper(gripper_width=120)\n",
"await pf.arm.close_gripper(gripper_width=80)\n",
"await pf.arm.backend.is_gripper_closed()"
]
"outputs": [],
"source": "await pf.arm.move_gripper(width=120, force_sensing=False)\nawait pf.arm.move_gripper(width=80, force_sensing=True)\nawait pf.arm.backend.is_gripper_closed()"
},
{
"cell_type": "code",
Expand All @@ -126,13 +90,13 @@
"name": "stdout",
"output_type": "stream",
"text": [
"PreciseFlexGripperLocation(location=Coordinate(x=262.0253, y=0.0042, z=1.501), rotation=Rotation(x=-180, y=90, z=0.0010000000000864489), rail=0.015, orientation='left', wrist='ccw')\n",
"PreciseFlexCartesianPose(location=Coordinate(x=262.0253, y=0.0042, z=1.501), rotation=Rotation(x=-180, y=90, z=0.0010000000000864489), rail=0.015, orientation='left', wrist='ccw')\n",
"{<PFAxis.RAIL: 6>: 0.015, <PFAxis.BASE: 1>: 1.501, <PFAxis.SHOULDER: 2>: 72.977, <PFAxis.ELBOW: 3>: 199.323, <PFAxis.WRIST: 4>: -272.299, <PFAxis.GRIPPER: 5>: 79.8}\n"
]
}
],
"source": [
"c = await pf.arm.backend.request_gripper_location()\n",
"c = await pf.arm.backend.request_gripper_pose()\n",
"j = await pf.arm.backend.request_joint_position()\n",
"print(c)\n",
"print(j)"
Expand Down Expand Up @@ -173,7 +137,7 @@
{
"data": {
"text/plain": [
"PreciseFlexGripperLocation(location=Coordinate(x=262.0253, y=0.0042, z=1.501), rotation=Rotation(x=-180, y=90, z=0.0010000000000864489), rail=0.015, orientation='right', wrist='ccw')"
"PreciseFlexCartesianPose(location=Coordinate(x=262.0253, y=0.0042, z=1.501), rotation=Rotation(x=-180, y=90, z=0.0010000000000864489), rail=0.015, orientation='right', wrist='ccw')"
]
},
"execution_count": 7,
Expand Down Expand Up @@ -391,4 +355,4 @@
},
"nbformat": 4,
"nbformat_minor": 5
}
}
68 changes: 54 additions & 14 deletions docs/user_guide/capabilities/arms.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# Arms

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

- {class}`~pylabrobot.capabilities.arms.arm.GripperArm` -- a fixed-axis gripper arm (e.g. Hamilton core grippers). Grips along a single axis.
- {class}`~pylabrobot.capabilities.arms.orientable_arm.OrientableArm` -- a rotatable gripper arm (e.g. Hamilton iSWAP). Can grip from any direction.
- {class}`~pylabrobot.capabilities.arms.arm.FixedAxisGripperArm`: a fixed-axis gripper arm (e.g. Hamilton core grippers). Grips along a single deck-fixed axis.
- {class}`~pylabrobot.capabilities.arms.orientable_arm.OrientableGripperArm`: a rotatable gripper arm (e.g. Hamilton iSWAP). Can grip from any direction.
- {class}`~pylabrobot.capabilities.arms.articulated_arm.ArticulatedGripperArm`: a fully articulated arm (e.g. UFACTORY xArm 6). Pick/drop with arbitrary 3D rotation.

Both inherit from `_BaseArm`, which is a {class}`~pylabrobot.capabilities.capability.Capability`.
All three inherit from {class}`~pylabrobot.capabilities.arms.arm.GripperArm` (the abstract base that owns gripper-width control), which in turn extends `_BaseArm`, a {class}`~pylabrobot.capabilities.capability.Capability`.

## When to use

Use arms to move plates, lids, and other labware between deck positions -- from a hotel to a reader, from a reader to a shaker, from a shaker to a centrifuge, etc.
Use arms to move plates, lids, and other labware between deck positions: from a hotel to a reader, from a reader to a shaker, from a shaker to a centrifuge, etc.

## Setup

Expand All @@ -21,7 +22,7 @@ from pylabrobot.hamilton.star import STAR
lh = STAR(name="star", ...)
await lh.setup()

# the arm is at lh.iswap (OrientableArm) or lh.core_gripper (GripperArm)
# the arm is at lh.iswap (OrientableGripperArm) or lh.core_gripper (FixedAxisGripperArm)
await lh.iswap.move_resource(plate, to=heater_shaker)
```

Expand Down Expand Up @@ -50,22 +51,61 @@ await lh.iswap.move_resource(
)
```

### OrientableArm: grip direction
### OrientableGripperArm: grip direction

```python
from pylabrobot.capabilities.arms.standard import GripDirection
`direction` is the world yaw of the gripper's front finger, in degrees,
**CCW about +Z with 0° = +X**. You can pass a float, or one of the
cardinal-direction strings:

await lh.iswap.pick_up_resource(plate, direction=GripDirection.LEFT)
await lh.iswap.drop_resource(reader, direction=GripDirection.FRONT)
```python
await lh.iswap.pick_up_resource(plate, direction="left") # = 180°
await lh.iswap.drop_resource(reader, direction="front") # = 270°
await lh.iswap.move_to_location(coord, direction=45.0) # 45° CCW from +X
```

| String | Degrees | World axis (deck frame) |
|:------------|--------:|:------------------------|
| `"right"` | `0°` | `+X` |
| `"back"` | `90°` | `+Y` |
| `"left"` | `180°` | `-X` |
| `"front"` | `270°` | `-Y` |

## Tips and gotchas

- **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.
- **`pickup_distance_from_bottom`** controls how far up from the bottom of the resource the gripper grips. If `None`, the resource's `preferred_pickup_location` is used, or a default of 5 mm from the top (`size_z - 5`).
- **Resource tree is updated automatically.** After a successful `drop_resource`, the resource is unassigned from its old parent and assigned to the destination.
- **`GripOrientation`** is either a {class}`~pylabrobot.capabilities.arms.standard.GripDirection` enum (`FRONT`, `RIGHT`, `BACK`, `LEFT`) or a float in degrees.
- **`request_gripper_location()`** queries the hardware for the current end effector position. `get_picked_up_resource()` returns the internally tracked state (no hardware call).
- **`GripperOrientation`** is either a {data}`~pylabrobot.capabilities.arms.standard.GripperDirection` string literal (`"front"`, `"right"`, `"back"`, `"left"`) or a float in degrees, measured CCW about world +Z with 0° = +X.
- **`request_gripper_pose()`** queries the hardware for the current end effector position. `get_picked_up_resource()` returns the internally tracked state (no hardware call).

## Grippers

Gripper actuation goes through a single fundamental call, `move_gripper(width, force_sensing)`, which has two modes:

- `force_sensing=False`: drive the jaws to `width` mm without force feedback. The jaws reach exactly that width.
- `force_sensing=True`: close toward `width` mm with force feedback, stopping on contact. The final width may be larger than the target.

Widths are always in mm. Each backend declares its hardware limits via `min_gripper_width` and `max_gripper_width` (either may be `None` if the gripper has no commandable open/close at that end).

`open_gripper()` and `close_gripper()` are convenience wrappers built on top: `open_gripper()` calls `move_gripper(max_gripper_width, force_sensing=False)` and `close_gripper()` calls `move_gripper(min_gripper_width, force_sensing=True)`. They take no width but still forward `backend_params` so backend-specific options (e.g. `iSWAPBackend.GripParams`) work the same as with `move_gripper`. They raise `NotImplementedError` if the corresponding limit is `None`.

## Migration

The arm capability was reshaped in the v1b1 release. The old
`pylabrobot.arms` shim package has been removed; import from
`pylabrobot.capabilities.arms.*` instead.

| Old | New |
|:----|:----|
| `pylabrobot.arms.GripperArm` (concrete) | {class}`~pylabrobot.capabilities.arms.arm.FixedAxisGripperArm` |
| `pylabrobot.arms.OrientableArm` | {class}`~pylabrobot.capabilities.arms.orientable_arm.OrientableGripperArm` |
| `pylabrobot.arms.ArticulatedArm` | {class}`~pylabrobot.capabilities.arms.articulated_arm.ArticulatedGripperArm` |
| `iSWAPBackend.CloseGripperParams` | {class}`~pylabrobot.hamilton.liquid_handlers.star.iswap.iSWAPBackend.GripParams` |
| `open_gripper(width=...)` / `close_gripper(width=...)` | `move_gripper(width=..., force_sensing=...)` |

The name `GripperArm` still exists in `pylabrobot.capabilities.arms.arm` but
now refers to the abstract base. Code that did `class MyArm(GripperArm)` over
the old concrete class should subclass `FixedAxisGripperArm` instead.

## Supported hardware

Expand All @@ -74,4 +114,4 @@ await lh.iswap.drop_resource(reader, direction=GripDirection.FRONT)

## API reference

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`.
See {class}`~pylabrobot.capabilities.arms.arm.GripperArm`, {class}`~pylabrobot.capabilities.arms.arm.FixedAxisGripperArm`, {class}`~pylabrobot.capabilities.arms.orientable_arm.OrientableGripperArm`, {class}`~pylabrobot.capabilities.arms.articulated_arm.ArticulatedGripperArm`, {class}`~pylabrobot.capabilities.arms.backend.GripperArmBackend`, and {class}`~pylabrobot.capabilities.arms.backend.OrientableGripperArmBackend`.
4 changes: 2 additions & 2 deletions docs/user_guide/hamilton/star/core-grippers.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"source": [
"## Moving resources\n",
"\n",
"Use {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",
"Use {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.FixedAxisGripperArm` 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."
Expand Down Expand Up @@ -157,4 +157,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
}
}
5 changes: 2 additions & 3 deletions docs/user_guide/hamilton/star/hardware/replacing-iswap.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,10 @@ await star.driver.iswap.reengage_break() # firmware command "R0BO"

```python
from pylabrobot.hamilton.liquid_handlers.star.iswap import iSWAPBackend
from pylabrobot.capabilities.arms.standard import GripDirection

await star.driver.iswap.rotate(
rotation_drive=iSWAPBackend.RotationDriveOrientation.RIGHT,
grip_direction=GripDirection.BACK,
grip_direction="back",
)
```

Expand All @@ -97,7 +96,7 @@ await star.driver.iswap.rotate(
```python
await star.driver.iswap.rotate(
rotation_drive=iSWAPBackend.RotationDriveOrientation.LEFT,
grip_direction=GripDirection.BACK,
grip_direction="back",
)
```

Expand Down
Loading
Loading