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
2 changes: 2 additions & 0 deletions docs/api/pylabrobot.formulatrix.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Mantis
driver.simulator.MantisSimulationLedBar
driver.features.arm.Arm
driver.arm_kinematics
driver.stage_transform
driver.sequence_files
driver.features.plate_clamp.PlateClamp
driver.features.pump.Pump
driver.features.rfid.Rfid
Expand Down
122 changes: 107 additions & 15 deletions docs/user_guide/formulatrix/mantis/hello-world.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"source": [
"# Formulatrix Mantis\n",
"\n",
"The Mantis is a liquid dispenser from Formulatrix that dispenses through exchangeable chips. PyLabRobot connects to it, identifies it, reads its firmware versions, name, motors and pressure sensors, brings the arm and Z up and parks them at stop (v4), locks and unlocks the plate clamp, reads which chip sits in each input slot, and shows colours on a v4's LED bar. Nothing else in this driver moves the device, switches the pump on or opens a valve."
"The Mantis is a liquid dispenser from Formulatrix that dispenses through exchangeable chips. PyLabRobot connects to it, identifies it, reads its firmware versions, name, motors and pressure sensors, brings the arm and Z up, moves the nozzle in the deck frame, parks it from any zone and moves the head into the waste and either wash position (v4), locks and unlocks the plate clamp, reads which chip sits in each input slot, and shows colours on a v4's LED bar. Nothing in this driver switches the pump on or opens a valve."
]
},
{
Expand All @@ -25,7 +25,7 @@
"metadata": {},
"source": [
"```{warning}\n",
"This driver has run on one Mantis v4, on macOS and on Windows; no v3 has run it yet. The arm's bring-up and the stop that parks it have not yet run on a device through this driver; run them with the head clear and someone at the power switch. `setup()` logs a warning on a firmware set not seen on a Mantis yet, which includes every v3. If you run this guide on a Mantis, please [report the result](../../needs-testing.md).\n",
"This driver has run on one Mantis v4, on macOS and on Windows; no v3 has run it yet. The arm's bring-up, its moves, the waste, the washes and the stop that parks it have not yet run on a device through this driver; run them with the head clear and someone at the power switch. `setup()` logs a warning on a firmware set not seen on a Mantis yet, which includes every v3. If you run this guide on a Mantis, please [report the result](../../needs-testing.md).\n",
"```"
]
},
Expand All @@ -42,12 +42,12 @@
"| Reads | firmware versions, device name, motor limits and state, the arm's absolute encoders (v4), line pressures and the pressure board's state |\n",
"| Interface | USB, an FTDI bridge (VID 0x0403 / PID 0x6010): interface B for commands, interface A for the RFID reader |\n",
"| Sequencer | queues PPI writes and moves of the three motors, and waits until each is played; internal, for the features that act |\n",
"| Arm (v4) | brought up at setup from where its absolute encoders read; Z homed; parked at idle at stop |\n",
"| Arm (v4) | brought up at setup from where its absolute encoders read; Z homed; the nozzle moved in the deck frame, across or up and down, through the unit's stage transform; parked along the unit's routes from any zone; moved into the waste and either wash position along the unit's approach paths, from wherever it stands |\n",
"| Plate clamp | lock at one of the unit's levels, waiting for clamping done; unlock |\n",
"| RFID | the chip in each input slot: serial, chip type, date of manufacture |\n",
"| LED bar (v4) | one colour, one per LED, or an animation; shows setup, with the arm's bring-up; off at stop |\n",
"| Unit configuration | the clamp's lock levels, each slot's RFID antenna and the arm's calibration, from the unit's configuration folder or a saved file; the first two else from a recording of its generation |\n",
"| Not supported yet | moving the arm to a position, switching the pump or a pressure controller, chips, dispensing |"
"| Unit configuration | the clamp's lock levels, each slot's RFID antenna, the arm's calibration, its routes and the stage transform, from the unit's configuration folder or a saved file; the first two else from a recording of its generation |\n",
"| Not supported yet | switching the pump or a pressure controller, chips, dispensing |"
]
},
{
Expand Down Expand Up @@ -81,9 +81,9 @@
"source": [
"## Unit configuration\n",
"\n",
"Some of a Mantis's values are set per unit and cannot be read off the device; they sit in the unit's `.config` files, in `Data/Device/Configs`. Give setup that folder once, then save the configuration: the saved file carries the unit's values from then on, and the folder is no longer needed. So far these are the plate clamp's lock levels, each input slot's RFID antenna and, on a v4, the arm's calibration: each arm motor's offset, absolute encoder reference and encoder correction.\n",
"Some of a Mantis's values are set per unit and cannot be read off the device; they sit in the unit's `.config` files, in `Data/Device/Configs`. Give setup that folder once, then save the configuration: the saved file carries the unit's values from then on, and the folder is no longer needed. So far these are the plate clamp's lock levels, each input slot's RFID antenna and, on a v4, the arm's calibration: each arm motor's offset, absolute encoder reference and encoder correction, and the routes the head takes: each zone's route to idle and the approach paths to the waste and the two wash positions; and the stage transform that places the nozzle: its transformation matrix and tilt, and the plate holder's origin.\n",
"\n",
"Without the clamp's levels or the antennas, a recording of a unit of the same generation supplies them, with a warning: the Mantis runs, less accurately. The arm's calibration never comes from a recording: without it the arm is not brought up and setup refuses. Where the folder and a saved file both hold a value, they must agree, or setup refuses. A file saved by an earlier version still loads: a value it lacks comes from the folder, else the recording, with one warning naming it."
"Without the clamp's levels or the antennas, a recording of a unit of the same generation supplies them, with a warning: the Mantis runs, less accurately. The arm's calibration, its routes and the stage transform never come from a recording: without the calibration the arm is not brought up and setup refuses; without the routes the head is moved to idle only from near it, and the waste and the washes refuse; without the stage transform the nozzle's moves refuse. Where the folder and a saved file both hold a value, they must agree, or setup refuses. A file saved by an earlier version still loads: a value it lacks comes from the folder, else the recording, with one warning naming it."
]
},
{
Expand All @@ -105,7 +105,7 @@
"source": [
"## Setup\n",
"\n",
"`setup()` opens both links, reads the firmware versions, the motor limits and the pressure sensors' calibration, takes the unit's values and sets up the RFID reader. On a v4 it then brings the arm and Z up, as in the recorded v4 startup: it energises the arm motors, declares them where their absolute encoders read, moves them to idle, homes Z, and moves to idle again. It does so only if the head stands near idle, in front of or behind it; from anywhere else it refuses, the arm unpowered: park the arm at idle first. `setup(skip_arm=True)` reads only and moves nothing. On a v4 the LED bar shows the setup, the arm's bring-up with it, green for a second at its end, then the idle animation."
"`setup()` opens both links, reads the firmware versions, the motor limits and the pressure sensors' calibration, takes the unit's values and sets up the RFID reader. On a v4 it then brings the arm and Z up, as in the recorded v4 startup: it energises the arm motors, declares them where their absolute encoders read, moves them to idle, homes Z, and moves to idle again. From wherever the head stands it follows its zone's routes. Without the unit's routes it does so only if the head stands near idle, in front of or behind it; from anywhere else it refuses, the arm unpowered: park the arm at idle first. `setup(skip_arm=True)` reads only and moves nothing. On a v4 the LED bar shows the setup, the arm's bring-up with it, green for a second at its end, then the idle animation."
]
},
{
Expand Down Expand Up @@ -186,7 +186,7 @@
"source": [
"## Visualizer\n",
"\n",
"Show the Mantis in the 3D visualizer. `start()` serves it and opens it in a browser."
"Show the Mantis in the 3D visualizer. `start()` serves it and opens it in a browser. After each arm move the arm's links and the Z column take where the encoders read."
]
},
{
Expand Down Expand Up @@ -259,7 +259,7 @@
"metadata": {},
"outputs": [],
"source": [
"mantis.driver.configuration.motor_limits"
"mantis.arm.configuration.motor_limits"
]
},
{
Expand Down Expand Up @@ -385,7 +385,7 @@
"metadata": {},
"outputs": [],
"source": [
"await mantis.arm.request_joint_position()"
"await mantis.arm.request_joint_state()"
]
},
{
Expand All @@ -403,7 +403,7 @@
"metadata": {},
"outputs": [],
"source": [
"await mantis.arm.request_absolute_joint_position()"
"await mantis.arm.request_absolute_joint_state()"
]
},
{
Expand All @@ -413,7 +413,7 @@
"metadata": {},
"outputs": [],
"source": [
"await mantis.arm.request_head_position()"
"await mantis.arm.request_pose()"
]
},
{
Expand Down Expand Up @@ -441,7 +441,69 @@
"metadata": {},
"outputs": [],
"source": [
"mantis.arm.default_arm_speed, mantis.arm.default_z_speed"
"mantis.arm.default_xy_speed, mantis.arm.default_z_speed"
]
},
{
"cell_type": "markdown",
"id": "arm-moves",
"metadata": {},
"source": [
"### Moving the arm (v4)\n",
"\n",
"The nozzle moves in the deck frame, mm: `move_to_xy_position` moves it across, `move_to_z_position` up or down with the arms held. Every move across is guarded, as on the STAR: Z first rises to `minimum_traverse_height_start` (`default_minimum_traverse_height` when None, else the safe height) if the nozzle is below it. Over the plate holder the unit's stage transform places it; elsewhere the arm frame fitted to that stage. `move_to_location` also lowers Z to the location's height after. Speeds are the nozzle's, in mm/s; without one the arms move at the route profile. Every target is checked against the motor limits, and Z's speed and acceleration against the range of the recorded moves (1 to 55 mm/s, 200 to 500 mm/s²), before anything is sent. Nothing models the chip racks: move only with the path clear."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "arm-move-to-location",
"metadata": {},
"outputs": [],
"source": [
"pose = await mantis.arm.request_pose()\n",
"await mantis.arm.move_to_xy_position(x=pose.nozzle_location.x, y=pose.nozzle_location.y - 20.0)\n",
"await mantis.arm.park()"
]
},
{
"cell_type": "markdown",
"id": "arm-park",
"metadata": {},
"source": [
"`park()` moves the head to idle along its zone's route, rising first if it is low; `request_is_parked()` reads whether it stands there. `recover_from_fault()` recovers after a motor error as setup does."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "arm-park-code",
"metadata": {},
"outputs": [],
"source": [
"await mantis.arm.park()\n",
"await mantis.arm.request_is_parked()"
]
},
{
"cell_type": "markdown",
"id": "arm-approach-paths",
"metadata": {},
"source": [
"`move_to_waste()` and `move_to_wash(position=...)` follow the unit's approach paths from wherever the head stands: from one wash position to the other it stays in the wash. `park()` takes the head back out along the path it is on, then to idle."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "arm-approach-paths-code",
"metadata": {},
"outputs": [],
"source": [
"await mantis.arm.move_to_waste()\n",
"await mantis.arm.park()\n",
"await mantis.arm.move_to_wash(position=1)\n",
"await mantis.arm.park()"
]
},
{
Expand Down Expand Up @@ -572,6 +634,36 @@
"await mantis.plate_clamp.request_state()"
]
},
{
"cell_type": "markdown",
"id": "arm-wellplate",
"metadata": {},
"source": [
"### Moving over a wellplate (v4)\n",
"\n",
"A well's centre in the deck frame comes from the resource model; the wellplate is taken to be clamped with A1 at the back left. Heights are those of a chip's nozzle, from the unit's calibration; where the head stands without a chip is not measured, so stay high, here 12.5 mm above the wellplate. Start from idle with the path clear."
]
},
{
"cell_type": "code",
"id": "arm-wellplate-code",
"metadata": {},
"source": [
"from pylabrobot.resources.corning import cor_96_wellplate_360uL_Fb\n",
"\n",
"wellplate = cor_96_wellplate_360uL_Fb(name=\"wellplate\")\n",
"mantis.plate_holder.assign_child_resource(wellplate)\n",
"top = wellplate.get_location_wrt(mantis.deck, z=\"t\").z\n",
"await mantis.arm.park()\n",
"for name in (\"A1\", \"H12\"):\n",
" center = wellplate.get_item(name).get_location_wrt(mantis.deck, x=\"c\", y=\"c\")\n",
" await mantis.arm.move_to_xy_position(x=center.x, y=center.y)\n",
" await mantis.arm.move_to_z_position(z=top + 12.5)\n",
"await mantis.arm.park()"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "mantis-clamp-unlock-text",
Expand Down Expand Up @@ -826,7 +918,7 @@
"source": [
"## Stop\n",
"\n",
"Stop the visualizer and close the link. On a v4, `stop()` first aborts the sequencer and stops every motor; an arm brought up is then parked at idle and Z lowered to 20 mm, as in the recorded close, unless the head is away from idle. Then the plate clamp is released."
"Stop the visualizer and close the link. On a v4, `stop()` first aborts the sequencer and stops every motor; an arm brought up is then parked at idle and Z lowered to 20 mm, as in the recorded close; from away from idle the head first follows its zone's route, and without the unit's routes it is not parked. Then the plate clamp is released."
]
},
{
Expand Down
Loading
Loading