{ "cells": [ { "cell_type": "markdown", "id": "intro", "metadata": {}, "source": [ "# HighRes LidValet\n", "\n", "The HighRes Biosolutions **LidValet** is a benchtop delidder. Each of its nests (the\n", "controller calls them *hotels*) holds a vacuum suction cup above a plate position: a mover\n", "presents a lidded plate to a nest, the LidValet lifts the lid off into the cup and holds it\n", "there, and later lowers it back onto the plate.\n", "\n", "| | |\n", "|---|---|\n", "| Communication | TCP socket |\n", "| Default address | `192.168.127.60:1000` (printed on the back of the device) |\n", "| Protocol | newline-terminated ASCII, `ACK!` / `OK!` / `ERROR!` |\n", "| Nests | read from the device (`ACTIVE_HOTELS`) |\n", "| Nest states | `open`, `has_lid`, `busy`, `error`, `unknown` |\n", "\n", "[OEM link](https://www.highres.com/lab-instruments/lid-management)" ] }, { "cell_type": "markdown", "id": "physical", "metadata": {}, "source": [ "## Physical setup\n", "\n", "The LidValet is a network device: connect it to the host with an Ethernet cable, either\n", "directly or through a switch. It does **not** run a DHCP client, so give the host an address\n", "on the device's subnet, for example:\n", "\n", "```\n", "sudo ip addr add 192.168.127.1/24 dev \n", "```\n", "\n", "The address on the label is the one to talk to. A unit that has been sitting powered but\n", "idle can hold link while answering nothing at all \u2014 if it is silent, power-cycle it and it\n", "will announce itself on boot." ] }, { "cell_type": "markdown", "id": "connect", "metadata": {}, "source": [ "## Connect\n", "\n", "`setup()` opens the socket, reads the nest count off the device, refuses to start if a lid\n", "is stuck in a suction cup, and resets every nest. Pass `host` and `port` for your machine \u2014\n", "the nest count is not something you configure, it comes from the device itself." ] }, { "cell_type": "code", "id": "connect-code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pylabrobot.high_res import HighResLidValet\n", "\n", "valet = HighResLidValet(host=\"192.168.127.60\", port=1000)\n", "await valet.setup()\n", "\n", "print(valet.num_nests)" ] }, { "cell_type": "markdown", "id": "status", "metadata": {}, "source": [ "## Nest state\n", "\n", "`request_state()` returns one nest's state; `request_all_states()` returns every nest in a\n", "single round trip. `request_has_lid()` and `request_is_busy()` are convenience wrappers.\n", "\n", "A nest reports `error` when its last operation failed \u2014 it stays that way until the nest is\n", "reset." ] }, { "cell_type": "code", "id": "status-code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(await valet.request_state(1))\n", "print(await valet.request_all_states())" ] }, { "cell_type": "markdown", "id": "delid", "metadata": {}, "source": [ "## Delid a plate\n", "\n", "With a lidded plate on a nest, `delid()` lifts the lid into that nest's suction cup. The cup\n", "must be empty; the call raises if it is already holding a lid or if the nest is busy." ] }, { "cell_type": "code", "id": "delid-code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await valet.delid(nest=1)" ] }, { "cell_type": "markdown", "id": "lid", "metadata": {}, "source": [ "## Re-lid the plate\n", "\n", "`lid()` lowers the held lid back onto the plate. The cup must be holding a lid. It retries a\n", "few times to ride out a transient busy state, matching the device server's own behaviour." ] }, { "cell_type": "code", "id": "lid-code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await valet.lid(nest=1)" ] }, { "cell_type": "markdown", "id": "reset", "metadata": {}, "source": [ "## Reset\n", "\n", "`reset()` homes a nest and clears its error state. Called with no argument it resets every\n", "nest in one command, which is also what `setup()` does." ] }, { "cell_type": "code", "id": "reset-code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await valet.reset(nest=1)\n", "await valet.reset()" ] }, { "cell_type": "markdown", "id": "motion", "metadata": {}, "source": [ "## Homing and lift motion\n", "\n", "`home()` homes the whole system. `wave()` drops and raises the lifts a given number of times\n", "\u2014 useful for confirming the machine is alive and that the pneumatics are connected. Both\n", "block until the motion is finished." ] }, { "cell_type": "code", "id": "motion-code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await valet.home()\n", "await valet.wave(cycles=3)" ] }, { "cell_type": "markdown", "id": "pneumatics", "metadata": {}, "source": [ "## Vacuum and purge\n", "\n", "The suction cups are driven by solenoid outputs, which `delid()` and `lid()` operate for\n", "you. `set_vacuum()` and `set_purge()` drive them directly, which is mainly useful when\n", "diagnosing a cup that will not pick up.\n", "\n", "These return immediately: the controller switches the output and does not wait, and on a\n", "machine with no vacuum sensor fitted it cannot confirm suction was achieved." ] }, { "cell_type": "code", "id": "pneumatics-code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await valet.set_vacuum(nest=1, on=True)\n", "await valet.set_vacuum(nest=1, on=False)" ] }, { "cell_type": "markdown", "id": "diagnostics", "metadata": {}, "source": [ "## Diagnostics\n", "\n", "The controller keeps an error stack and a command history, and reports its own versions.\n", "When something fails, `request_errors()` is the first place to look \u2014 the device's own message\n", "is more specific than the exception text." ] }, { "cell_type": "code", "id": "diagnostics-code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(await valet.request_version())\n", "print(await valet.request_errors(5))" ] }, { "cell_type": "markdown", "id": "settings", "metadata": {}, "source": [ "## Settings\n", "\n", "The device exposes its full calibration and configuration. `request_settings()` returns it as a\n", "typed, frozen object, one attribute per device setting." ] }, { "cell_type": "code", "id": "settings-code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "settings = await valet.request_settings()\n", "print(settings.serial_number, settings.active_hotels)" ] }, { "cell_type": "markdown", "id": "teardown", "metadata": {}, "source": [ "## Disconnect\n", "\n", "`stop()` asks the server to drop this client and then closes the socket, so the controller\n", "frees the connection slot rather than waiting for it to time out." ] }, { "cell_type": "code", "id": "teardown-code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await valet.stop()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.10" } }, "nbformat": 4, "nbformat_minor": 5 }