Skip to content
Open
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
14 changes: 14 additions & 0 deletions docs/_static/devices.json
Original file line number Diff line number Diff line change
Expand Up @@ -2153,6 +2153,20 @@
"doc_slug": "li_cor/odyssey/hello-world",
"notes": "HTTP scan control and TIFF retrieval; port pending hardware verification."
},
{
"id": "thermo-fisher-kingfisher-flex",
"vendor": "Thermo Fisher",
"name": "KingFisher Flex",
"kind": "magnetic particle processor",
"capabilities": ["magnetic bead processing"],
"status": "wip",
"needs_hardware_testing": true,
"api": "pylabrobot.thermo_fisher.kingfisher.flex.KingFisherFlex",
"api_version": "v1",
"code_slug": "thermo_fisher/kingfisher/flex",
"doc_slug": "thermo_fisher/kingfisher/flex/hello-world",
"notes": "96DW local BDZ execution tested on macOS/firmware 1.01.0, including 37 C heating, beads, drying and PLR confirmations. Partial tip pickup remains unresolved; broader formats/features and general protocol authoring remain unsupported."
},
{
"id": "thermo-fisher-kingfisher-presto",
"vendor": "Thermo Fisher",
Expand Down
25 changes: 25 additions & 0 deletions docs/api/pylabrobot.thermo_fisher.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,31 @@ KingFisher Presto
ProtocolStep
ProtocolTimeLeft

KingFisher Flex
---------------

.. currentmodule:: pylabrobot.thermo_fisher.kingfisher.flex

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

KingFisherFlex
FlexError
FlexEvent
FlexRunResult
FlexTranscriptEntry
FlexProtocol
FlexProtocolError
FlexProtocolIssue
FlexCommand
FlexPlate
FlexDispense
FlexTip
FlexPrompt
read_flex_bdz

BindIt protocol files
---------------------

Expand Down
250 changes: 250 additions & 0 deletions docs/user_guide/thermo_fisher/kingfisher/flex/hello-world.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "76d09de2",
"metadata": {},
"source": [
"# KingFisher Flex\n",
"\n",
"```{device-card} thermo-fisher-kingfisher-flex\n",
"```\n",
"\n",
"Run a local BindIt BDZ directly through PLR with explicit plate and protocol-pause confirmations.\n",
"The tested setup is a matching **96DW head, tip comb and plates**, firmware 1.01.0 on macOS.\n",
"Hardware checks include mixing, bead collection/release, both drying positions, 37 C heating,\n",
"and a reagent-addition pause. Other documented temperature setpoints are accepted but not\n",
"individually hardware-tested. Linux and other head/plate formats remain unvalidated.\n",
"\n",
"**Experimental driver:** partial tip-comb attachment may occur without a firmware error.\n",
"Command completion does not verify attachment. Inspect pickup and\n",
"physical clearances; there is no automatic retry or recovery. See the\n",
"[support and operation guide](validation.md).\n",
"\n",
"The first version executes supported exports; general protocol authoring is deferred.\n",
"Onboard virtual Start and protocol upload remain unsupported.\n"
]
},
{
"cell_type": "markdown",
"id": "00b27c3c",
"metadata": {},
"source": [
"## How it communicates\n",
"\n",
"The Flex uses length-prefixed 64-byte HID reports carrying ASCII command lines. Command acknowledgment and execution completion are distinct. The driver continually reads responses, including temperature reports during motion and operator waits. It does not use the Presto XML protocol or its acknowledgment commands.\n",
"\n",
"## Physical setup\n",
"\n",
"Prepare the installed 96DW head, matching tips and supported plates according to the instrument instructions. Close BindIt before connecting PLR. On Linux install PLR with its HID extra, the system HIDAPI library, and device permissions as described in the validation guide. Each hardware cell below is a separate operation to review; do not run the entire notebook unattended."
]
},
{
"cell_type": "markdown",
"id": "45ae786f",
"metadata": {},
"source": [
"## Inspect a protocol offline\n",
"\n",
"Set the path to your original BindIt export. Reading and preflight do not open USB. Unknown script forms remain inspectable but cannot execute. Plate labels are not printed tray numbers."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4c82c377",
"metadata": {},
"outputs": [],
"source": [
"from pathlib import Path\n",
"from pylabrobot.thermo_fisher.kingfisher.flex import KingFisherFlex, read_flex_bdz\n",
"\n",
"protocol = read_flex_bdz(Path(\"protocol.bdz\"))\n",
"commands = protocol.preflight()\n",
"(protocol.name, protocol.sha256, len(commands), protocol.prompts())"
]
},
{
"cell_type": "markdown",
"id": "51bb761a",
"metadata": {},
"source": [
"## Connect and identify\n",
"\n",
"This cell accesses real hardware. `setup()` sends only VER; it does not run initialization or acknowledge faults. Specify `serial_number` if more than one instrument is connected."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c03729f7",
"metadata": {},
"outputs": [],
"source": [
"flex = KingFisherFlex()\n",
"await flex.setup()\n",
"(flex.version, flex.serial)"
]
},
{
"cell_type": "markdown",
"id": "b053d5bd",
"metadata": {},
"source": [
"## Initialize explicitly\n",
"\n",
"**This moves the instrument.** Check the instrument and clearances first. Initialization\n",
"has been tested on firmware 1.01.0; the observed final orientation is printed tray 1\n",
"at processing and tray 4 at loading. Initialization is separate from opening USB."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2ab44d80",
"metadata": {},
"outputs": [],
"source": [
"await flex.initialize()\n",
"flex.processing_slot"
]
},
{
"cell_type": "markdown",
"id": "42e2d8b8",
"metadata": {},
"source": [
"## Observe events\n",
"\n",
"Start one consumer before running. Host load/unload/pause events announce an action;\n",
"`host_prompt_completed` records its confirmation. Temperature events preserve heater and\n",
"ambient readings. They do not certify well-liquid temperature or target attainment."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8af32021",
"metadata": {},
"outputs": [],
"source": [
"import asyncio\n",
"import logging\n",
"\n",
"logging.basicConfig(level=logging.INFO)\n",
"\n",
"\n",
"async def observe_events():\n",
" async for event in flex.events():\n",
" logging.info(\"%s: %s; prompt=%s\", event.kind, event.text, event.prompt)\n",
"\n",
"\n",
"observer = asyncio.create_task(observe_events())"
]
},
{
"cell_type": "markdown",
"id": "bc0f00d7",
"metadata": {},
"source": [
"## Define operator confirmations\n",
"\n",
"This interactive example uses a separate input thread so instrument telemetry continues\n",
"while you respond. Returning exactly `True` confirms the action. Any other response stops\n",
"streaming and leaves the session uncertain; it does not automatically abort physical motion.\n",
"The same handler is passed separately for plate actions and protocol pauses."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "123ebf83",
"metadata": {},
"outputs": [],
"source": [
"async def confirm_action(prompt):\n",
" print(f\"{prompt.action.upper()}: {prompt.label}\")\n",
" print(f\"Loading tray {prompt.loading_slot}; processing tray {prompt.processing_slot}\")\n",
" answer = await asyncio.to_thread(\n",
" input, \"Complete the action, clear your hands, then type CONFIRM: \"\n",
" )\n",
" return answer.strip() == \"CONFIRM\""
]
},
{
"cell_type": "markdown",
"id": "3488faef",
"metadata": {},
"source": [
"## Execute the existing protocol\n",
"\n",
"This cell starts motion and may start heating before loading. Complete each requested\n",
"action before confirming in Python; do not press physical Start in this host-confirmed mode.\n",
"For reagent additions, the prompt includes the plate, reagent and volume per well.\n",
"Text mentioning seconds in a pause label does not set a timer.\n",
"\n",
"The await returns only after all script commands and finalization complete. The driver\n",
"serializes commands; validate your method's physical behavior and outcome for its intended use.\n",
"To use physical Start instead, omit both confirmation callbacks from `run_protocol`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5f73c396",
"metadata": {},
"outputs": [],
"source": [
"result = await flex.run_protocol(\n",
" protocol,\n",
" confirm_prompt=confirm_action,\n",
" confirm_pause=confirm_action,\n",
" operator_timeout=None,\n",
")\n",
"result"
]
},
{
"cell_type": "markdown",
"id": "8ff0e980",
"metadata": {},
"source": [
"## Abort an active run when needed\n",
"\n",
"Use `await flex.abort()` from a concurrently running task to request abort while a command is pending. The abort path sends the Feature/ESC abort sequence and awaits ABO END. In a script, keep the run as an `asyncio.Task` if another task must abort it.\n",
"\n",
"Canceling the Python task or interrupting a notebook cell **does not automatically stop the instrument**. Use explicit abort if the connection remains available, or the instrument controls if communication is lost. After any interruption inspect physical positions, call `stop()`, then establish a fresh session. Do not replay the unfinished script automatically."
]
},
{
"cell_type": "markdown",
"id": "59f31c80",
"metadata": {},
"source": [
"## Disconnect\n",
"\n",
"`stop()` closes USB and wakes the event observer. It is not a motion-stop operation; use it here after the run has completed or after explicitly handling interruption."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1e56dfff",
"metadata": {},
"outputs": [],
"source": [
"await flex.stop()\n",
"await observer"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading
Loading