{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Inheco Incubator Shaker\n", "\n", "The Inheco Incubator Shaker is a family of enclosed incubator devices with optional shaking, used for plate storage, temperature control, and orbital mixing.\n", "\n", "Multiple units can be stacked (up to 5 \"power credits\" per stack) and controlled through a single USB connection. All 4 variants share the same serial firmware and a single backend covers the entire family.\n", "\n", "| Model | PLR Name | Shaking | Plate Format | Power Credits |\n", "|---|---|---|---|---|\n", "| Incubator MP | `incubator_mp` | no | Microplate | 1.0 |\n", "| Incubator Shaker MP | `incubator_shaker_mp` | yes | Microplate | 1.6 |\n", "| Incubator DWP | `incubator_dwp` | no | Deepwell Plate | 1.25 |\n", "| Incubator Shaker DWP | `incubator_shaker_dwp` | yes | Deepwell Plate | 2.5 |\n", "\n", "See the [Inheco Incubator Shaker product page](https://www.inheco.com/incubator-shaker.html) for hardware specifications. Connect via USB-A/B (VID:PID `0403:6001`). Each stack requires one USB cable and one power cable to the bottom-most unit; units above connect to the unit below via 15-pin SUB-D connectors." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup\n", "\n", "Set the DIP switch on the back of the bottom-most unit to a unique 4-bit address (0--15). This address identifies the stack." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pylabrobot.legacy.storage.inheco import (\n", " IncubatorShakerStack,\n", " InhecoIncubatorShakerStackBackend,\n", ")\n", "\n", "backend = InhecoIncubatorShakerStackBackend(dip_switch_id=2)\n", "stack = IncubatorShakerStack(backend=backend)\n", "await stack.setup()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The stack auto-discovers all connected units during `setup()`. Pass `verbose=True` to see serial port, DIP switch verification, and unit composition." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Addressing units\n", "\n", "Access individual units by index. The stack reports how many units are connected:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "stack.num_units # e.g. 2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "unit_0 = stack[0]\n", "unit_1 = stack[1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Loading tray\n", "\n", "Each unit has a motorized loading tray (drawer) for plate access:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await stack[0].open()\n", "# ... load or unload plate ...\n", "await stack[0].close()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Check plate presence (reflection-based sensor)\n", "await stack[0].request_plate_in_incubator() # returns True/False" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Temperature control" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await stack[0].start_temperature_control(37)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "current = await stack[0].get_temperature()\n", "print(f\"{current:.1f} °C\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The device has three independent temperature sensors (`\"main\"`, `\"dif\"`, `\"boost\"`). You can also read a geometric `\"mean\"` of all three:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await stack[0].get_temperature(sensor=\"mean\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Block until temperature is reached\n", "await stack[0].wait_for_temperature(sensor=\"mean\", tolerance=0.2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await stack[0].stop_temperature_control()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Shaking\n", "\n", "Shaker models (MP Shaker and DWP Shaker) support programmable shaking patterns." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Orbital shaking at 800 rpm (default pattern)\n", "await stack[0].shake(rpm=800)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await stack[0].stop_shaking()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Predefined shaking patterns:\n", "\n", "| Pattern | Description |\n", "|---|---|\n", "| `orbital` | Circular shaking (default) |\n", "| `elliptical` | Elliptical motion |\n", "| `figure_eight` | Figure-eight (Lissajous) motion |\n", "| `linear_x` | Linear motion along X |\n", "| `linear_y` | Linear motion along Y |" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await stack[0].shake(pattern=\"figure_eight\", rpm=400)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await stack[0].stop_shaking()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Stack-level commands\n", "\n", "The stack frontend provides master commands that apply to all units at once:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Open/close all loading trays\n", "await stack.open_all()\n", "await stack.close_all()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Temperature control for all units\n", "await stack.start_all_temperature_control(target_temperature=37)\n", "await stack.get_all_temperatures() # {0: 37.1, 1: 36.8}\n", "await stack.stop_all_temperature_control()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Query states\n", "await stack.request_loading_tray_states() # {0: 'closed', 1: 'closed'}\n", "await stack.request_temperature_control_states() # {0: False, 1: False}\n", "await stack.request_shaking_states() # {0: False, 1: False}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Multiple stacks\n", "\n", "When using more than one physical stack, pass the serial port explicitly to avoid ambiguity:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "backend_a = InhecoIncubatorShakerStackBackend(dip_switch_id=2, port=\"/dev/cu.usbserial-130\")\n", "stack_a = IncubatorShakerStack(backend=backend_a)\n", "await stack_a.setup()\n", "\n", "backend_b = InhecoIncubatorShakerStackBackend(dip_switch_id=7, port=\"/dev/cu.usbserial-42\")\n", "stack_b = IncubatorShakerStack(backend=backend_b)\n", "await stack_b.setup()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Teardown" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await stack.stop()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This stops all temperature control and shaking before disconnecting from the stack." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.12.0" } }, "nbformat": 4, "nbformat_minor": 4 }