Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion docs/_static/devices.json
Original file line number Diff line number Diff line change
Expand Up @@ -1835,7 +1835,11 @@
"arm"
],
"status": "mostly",
"api_version": "v0",
"api_version": "v1",
"api": "pylabrobot.paa.KX2",
"code_slug": "paa/kx2",
"doc_slug": "paa/kx2/hello-world",
"needs_hardware_testing": false,
"manager": "https://discuss.pylabrobot.org/u/rickwierenga"
},
{
Expand Down
24 changes: 24 additions & 0 deletions docs/api/pylabrobot.paa.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.. currentmodule:: pylabrobot.paa

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

KX2
---

.. currentmodule:: pylabrobot.paa.kx2

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

KX2
KX2Gripper
KX2BarcodeReader
KX2Config
Axis
CartesianPose
CanError
IKError
KX2BarcodeReaderError
1 change: 1 addition & 0 deletions docs/api/pylabrobot.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Manufacturers
pylabrobot.micronic
pylabrobot.molecular_devices
pylabrobot.opentrons
pylabrobot.paa
pylabrobot.qinstruments
pylabrobot.revvity
pylabrobot.sartorius
Expand Down
1 change: 1 addition & 0 deletions docs/user_guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ mettler_toledo/index
micronic/index
molecular_devices/index
opentrons/index
paa/index
qinstruments/index
revvity/index
sartorius/index
Expand Down
11 changes: 11 additions & 0 deletions docs/user_guide/paa/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# PAA

```{toctree}
:maxdepth: 1

kx2/installation
kx2/hello-world
kx2/continuous-motion
kx2/proximity-sensor
kx2/barcode-reader
```
127 changes: 127 additions & 0 deletions docs/user_guide/paa/kx2/barcode-reader.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a23709b6",
"metadata": {},
"source": [
"# KX2 barcode reader\n",
"\n",
"The optional barcode reader is an independent serial device. Configure the reader for external triggering before connecting; PLR leaves its stored mode, symbologies and firmware read window unchanged. See [installation](installation.md) for finding the serial port."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1d1e367c",
"metadata": {},
"outputs": [],
"source": [
"from pylabrobot.paa.kx2 import KX2BarcodeReader\n",
"\n",
"reader = KX2BarcodeReader(port=\"/dev/ttyUSB0\")"
]
},
{
"cell_type": "markdown",
"id": "a19f2be0",
"metadata": {},
"source": [
"## Connect\n",
"\n",
"Setup opens 9600-baud, 8-N-1 serial with no RTS/CTS and requests the firmware version."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "27ce8e67",
"metadata": {},
"outputs": [],
"source": [
"await reader.setup()"
]
},
{
"cell_type": "markdown",
"id": "cd0ebce3",
"metadata": {},
"source": [
"## Scan\n",
"\n",
"Place the label in the reading area. `read_time` is a host deadline in seconds, not a change to the scanner's firmware read window. A no-read returns `None`; errors propagate. The trigger is switched off on success, timeout, failure and cancellation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "274f8d7a",
"metadata": {},
"outputs": [],
"source": [
"barcode = await reader.scan_barcode(read_time=5)\n",
"barcode"
]
},
{
"cell_type": "markdown",
"id": "464ad870",
"metadata": {},
"source": [
"## Read firmware version"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1ea3c392",
"metadata": {},
"outputs": [],
"source": [
"await reader.request_software_version()"
]
},
{
"cell_type": "markdown",
"id": "2cdefb98",
"metadata": {},
"source": [
"## Disconnect\n",
"\n",
"Stop joins any scan's trigger cleanup and closes the port."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0bf89968",
"metadata": {},
"outputs": [],
"source": [
"await reader.stop()"
]
},
{
"cell_type": "markdown",
"id": "d7fecf0d",
"metadata": {},
"source": [
"## Use the reader attached to an arm\n",
"\n",
"You can instead construct `KX2(barcode_port=\"/dev/ttyUSB0\")`; the arm then manages the reader lifecycle and exposes `await kx2.scan_barcode(read_time=5)`. Do not open the same serial port with both objects."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
166 changes: 166 additions & 0 deletions docs/user_guide/paa/kx2/continuous-motion.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "6f6bd53c",
"metadata": {},
"source": [
"# KX2 continuous motion\n",
"\n",
"Complete [hello world](hello-world.ipynb) through setup first. These cells assume a connected `kx2`.\n",
"\n",
"Streaming sends CANopen interpolation points at 8 ms intervals. Setup configures the drive buffers and synchronized motion channels."
]
},
{
"cell_type": "markdown",
"id": "13a5e9fa",
"metadata": {},
"source": [
"## Capture a starting pose\n",
"\n",
"All locations are absolute grip-center coordinates in millimeters, with the KX2 yaw convention described in hello world."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9ee496b4",
"metadata": {},
"outputs": [],
"source": [
"from pylabrobot.paa.kx2 import CartesianPose\n",
"from pylabrobot.resources import Coordinate, Rotation\n",
"\n",
"start = await kx2.request_gripper_pose()"
]
},
{
"cell_type": "markdown",
"id": "d15f5cc3",
"metadata": {},
"source": [
"## Linear Cartesian path\n",
"\n",
"After verifying a clear 5 mm vertical segment, run this move. Both speed and acceleration must be provided. The planner checks joint travel at the sampled poses; it does not check collisions."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "de87d1b2",
"metadata": {},
"outputs": [],
"source": [
"target = Coordinate(start.location.x, start.location.y, start.location.z + 5)\n",
"await kx2.move_to_location(target, start.rotation.z, path=\"linear\",\n",
" max_gripper_speed=5, max_gripper_acceleration=20)"
]
},
{
"cell_type": "markdown",
"id": "74892bb5",
"metadata": {},
"source": [
"## Rotate at a fixed grip center\n",
"\n",
"A linear path can change yaw while keeping the grip center in place. Set the tool offsets for your gripper and verify clearance for the rotation. Angular speed and acceleration use degrees per second and degrees per second squared; their defaults are 30 and 100."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e9119ca5",
"metadata": {},
"outputs": [],
"source": [
"current = await kx2.request_gripper_pose()\n",
"await kx2.move_to_location(\n",
" current.location, current.rotation.z + 1, path=\"linear\",\n",
" max_gripper_speed=5, max_gripper_acceleration=20,\n",
" max_gripper_angular_speed=1, max_gripper_angular_acceleration=2,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "02325b67",
"metadata": {},
"source": [
"## Smooth waypoints\n",
"\n",
"`move_through_waypoints` fits a smooth spline, so the curve can overshoot the line segments joining your points. Check the whole curve. Keep the first waypoint at the measured current pose."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "354d1fed",
"metadata": {},
"outputs": [],
"source": [
"current = await kx2.request_gripper_pose()\n",
"waypoints = [current, CartesianPose(\n",
" Coordinate(current.location.x, current.location.y, current.location.z + 5), current.rotation\n",
")]\n",
"await kx2.move_through_waypoints(waypoints, speed=5, acceleration=20)"
]
},
{
"cell_type": "markdown",
"id": "c41f93b8",
"metadata": {},
"source": [
"## Parametric paths\n",
"\n",
"`move_parametric(path_fn, duration)` samples absolute poses at times in seconds. It does not impose a speed or acceleration cap. The path must start at the measured pose and remain continuous and within travel and motion limits; this holds the measured pose without a displacement."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b0a6e8bc",
"metadata": {},
"outputs": [],
"source": [
"held = await kx2.request_gripper_pose()\n",
"\n",
"def hold_pose(t: float) -> CartesianPose:\n",
" \"\"\"Return the same absolute pose throughout the requested interval.\"\"\"\n",
" return held\n",
"\n",
"await kx2.move_parametric(hold_pose, duration=1)"
]
},
{
"cell_type": "markdown",
"id": "6f93ae2e",
"metadata": {},
"source": [
"## Disconnect"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6d320012",
"metadata": {},
"outputs": [],
"source": [
"await kx2.stop()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading
Loading