{ "cells": [ { "cell_type": "markdown", "id": "benchcel-intro", "metadata": {}, "source": [ "# Agilent BenchCel 4R quickstart\n", "\n", "The Agilent BenchCel 4R is a four-stacker microplate handler. PyLabRobot controls its arm, stacker mechanisms, grippers, teachpoints, status queries, and labware configuration over Ethernet.\n", "\n", "| Property | Value |\n", "|---|---|\n", "| Communication | Ethernet TCP |\n", "| Default port | `7612` |\n", "| Frame | command byte + 16-bit little-endian payload length + payload |\n", "| Configuration | Four stackers and one taught transfer position |\n", "| Labware | ANSI/SLAS-format plates with device-specific gripper settings |\n", "| Verified with | BenchCel 4R, firmware `3.2.20.0` |\n", "\n", "```{note}\n", "This driver was verified against a live BenchCel 4R and VWorks packet captures. The protocol is reverse-engineered rather than vendor-published, so verify motions at low risk on your own hardware before unattended operation.\n", "```" ] }, { "cell_type": "markdown", "id": "benchcel-device-card", "metadata": {}, "source": [ "```{device-card} agilent-benchcel-4r\n", "```" ] }, { "cell_type": "markdown", "id": "benchcel-communication", "metadata": {}, "source": [ "## How it communicates\n", "\n", "The BenchCel accepts binary command frames over TCP port 7612. Successful commands normally complete with a `0x69` acknowledgement; device faults arrive as `0x02` frames containing an ASCII error message. Motion acknowledgements are returned after the motion finishes.\n", "\n", "Only one effective control client should own the connection. Close VWorks before connecting from PyLabRobot, and stop PyLabRobot before reconnecting VWorks." ] }, { "cell_type": "markdown", "id": "benchcel-physical-setup", "metadata": {}, "source": [ "## Physical setup\n", "\n", "Before connecting:\n", "\n", "1. Keep the pendant's robot-disable button and the emergency stop accessible.\n", "2. Clear the arm and stacker workspace of people, tools, and loose labware.\n", "3. Turn on compressed air and confirm the stacker racks are correctly installed and supported.\n", "4. Connect the control computer to the BenchCel Ethernet network and confirm its IP address.\n", "5. Teach and verify the transfer position in VWorks. Record its numeric teachpoint ID.\n", "6. Confirm the physical plate matches the configured labware dimensions and gripper offsets.\n", "7. Close VWorks so it does not compete for the control session.\n", "\n", "```{warning}\n", "An incorrect teachpoint can send the arm toward a home-like position. Never copy a teachpoint ID from another instrument without verifying it on this BenchCel.\n", "```" ] }, { "cell_type": "markdown", "id": "benchcel-create-md", "metadata": {}, "source": [ "## Create the device\n", "\n", "Set the instrument IP address and the transfer teachpoint ID verified on your BenchCel. The `0x1E` value below was observed on one installation and must not be assumed to be universal." ] }, { "cell_type": "code", "execution_count": null, "id": "benchcel-create-code", "metadata": {}, "outputs": [], "source": [ "from pylabrobot.agilent import BenchCel4R\n", "\n", "BENCHCEL_IP = \"192.168.0.10\" # Replace with this instrument's address.\n", "TRANSFER_TEACHPOINT_ID = 0x1E # Replace with the ID verified in VWorks.\n", "\n", "benchcel = BenchCel4R(\n", " name=\"benchcel\",\n", " host=BENCHCEL_IP,\n", " loading_tray_teachpoint_id=TRANSFER_TEACHPOINT_ID,\n", ")" ] }, { "cell_type": "markdown", "id": "benchcel-connect-md", "metadata": {}, "source": [ "## Connect\n", "\n", "Open the TCP connection after VWorks is closed." ] }, { "cell_type": "code", "execution_count": null, "id": "benchcel-connect-code", "metadata": {}, "outputs": [], "source": [ "await benchcel.setup()" ] }, { "cell_type": "markdown", "id": "benchcel-status-md", "metadata": {}, "source": [ "## Check arm status\n", "\n", "Read the current theta, X, Z, and robot-gripper positions before commanding motion." ] }, { "cell_type": "code", "execution_count": null, "id": "benchcel-status-code", "metadata": {}, "outputs": [], "source": [ "arm_pose = await benchcel.request_arm_pose()\n", "arm_pose" ] }, { "cell_type": "markdown", "id": "benchcel-sensors-md", "metadata": {}, "source": [ "## Check the stacker sensors\n", "\n", "Query all four stackers. `plate_presence` is an analog-like value; `plate_present()` applies the driver's default threshold." ] }, { "cell_type": "code", "execution_count": null, "id": "benchcel-sensors-code", "metadata": {}, "outputs": [], "source": [ "sensor_statuses = await benchcel.request_all_stacker_sensor_statuses()\n", "[\n", " {\n", " \"stacker\": status.stacker,\n", " \"air_pressure\": status.air_pressure,\n", " \"plate_presence\": status.plate_presence,\n", " \"plate_present\": status.plate_present(),\n", " }\n", " for status in sensor_statuses.values()\n", "]" ] }, { "cell_type": "markdown", "id": "benchcel-labware-model-md", "metadata": {}, "source": [ "## Model the labware\n", "\n", "Use the PLR resource factory that matches the physical plate. This example uses the Corning 3603 definition; replace it with your exact model. If plates nest, also set the plate resource’s `stacking_z_height` to the measured vertical pitch so the resource stack geometry remains accurate." ] }, { "cell_type": "code", "execution_count": null, "id": "benchcel-labware-model-code", "metadata": {}, "outputs": [], "source": [ "from pylabrobot.resources import cor_96_wellplate_360uL_Fb\n", "\n", "\n", "def example_plate(name: str):\n", " return cor_96_wellplate_360uL_Fb(name)" ] }, { "cell_type": "markdown", "id": "benchcel-labware-config-md", "metadata": {}, "source": [ "## Configure the BenchCel labware profile\n", "\n", "Push geometry only after checking every value against the physical plate and the VWorks setup. Invalid or mismatched gripper settings can drop or damage labware." ] }, { "cell_type": "code", "execution_count": null, "id": "benchcel-labware-config-code", "metadata": {}, "outputs": [], "source": [ "await benchcel.set_labware(example_plate(\"benchcel_labware_profile\"))\n", "benchcel.labware_settings" ] }, { "cell_type": "markdown", "id": "benchcel-track-stacks-md", "metadata": {}, "source": [ "## Track the plates in a stacker\n", "\n", "Populate each `ResourceStack` in bottom-to-top order. The last assigned plate is the accessible top plate." ] }, { "cell_type": "code", "execution_count": null, "id": "benchcel-track-stacks-code", "metadata": {}, "outputs": [], "source": [ "stack_1 = benchcel.stacks[0]\n", "for index in range(3):\n", " stack_1.assign_child_resource(example_plate(f\"stack_1_plate_{index + 1}\"))\n", "\n", "[plate.name for plate in stack_1.children]" ] }, { "cell_type": "markdown", "id": "benchcel-home-md", "metadata": {}, "source": [ "## Home the motors\n", "\n", "Home only with the full workspace clear. The controller normally drops its TCP session during homing; `home()` reconnects and waits until the device responds again." ] }, { "cell_type": "code", "execution_count": null, "id": "benchcel-home-code", "metadata": {}, "outputs": [], "source": [ "await benchcel.home()" ] }, { "cell_type": "markdown", "id": "benchcel-downstack-md", "metadata": {}, "source": [ "## Downstack a plate\n", "\n", "Move the accessible top plate from stacker 1 to the taught transfer position." ] }, { "cell_type": "code", "execution_count": null, "id": "benchcel-downstack-code", "metadata": {}, "outputs": [], "source": [ "plate = await benchcel.downstack(stack_1)" ] }, { "cell_type": "markdown", "id": "benchcel-upstack-md", "metadata": {}, "source": [ "## Upstack a plate\n", "\n", "Return that plate from the taught transfer position to stacker 1. Confirm the plate is still present at the transfer position before running this command." ] }, { "cell_type": "code", "execution_count": null, "id": "benchcel-upstack-code", "metadata": {}, "outputs": [], "source": [ "await benchcel.upstack(stack_1)" ] }, { "cell_type": "markdown", "id": "benchcel-maintenance-md", "metadata": {}, "source": [ "## Stacker and teaching diagnostics\n", "\n", "The API also exposes absolute moves with `move_to_stacker()` and `move_to_teachpoint()`, robot-gripper positioning with `open_robot_gripper()` and `close_robot_gripper()`, stacker-clamp positioning with `open_stacker_clamps()` and `close_stacker_clamps()`, and teachpoint writes with `set_teachpoint()`. These are service and teaching operations rather than a hello-world workflow. In particular, opening a stacker's pneumatic clamps can release the entire plate stack. Use these methods only with the rack supported and the service procedure for your installation in hand." ] }, { "cell_type": "markdown", "id": "benchcel-disconnect-md", "metadata": {}, "source": [ "## Disconnect\n", "\n", "Close the TCP connection before opening VWorks or ending the protocol." ] }, { "cell_type": "code", "execution_count": null, "id": "benchcel-disconnect-code", "metadata": {}, "outputs": [], "source": [ "await benchcel.stop()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3" } }, "nbformat": 4, "nbformat_minor": 5 }