Skip to content

v1 Mantis: connect, identify, read the device, clamp the plate and read the chip tags - #1612

Open
BioCam wants to merge 4 commits into
PyLabRobot:mainfrom
BioCam:mantis-v1-connect-clamp
Open

BioCam wants to merge 4 commits into
PyLabRobot:mainfrom
BioCam:mantis-v1-connect-clamp

Conversation

@BioCam

@BioCam BioCam commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator

The Problem

PyLabRobot has no way to drive a Formulatrix Mantis yet. #987 adds a Mantis backend with the full dispense workflow on a v3, built on the earlier machine and backend structure. The Mantis is a large device: arm, chip changer, pressure system, plate clamp and RFID reader, in two generations (v3 and v4) that differ in hardware. This PR starts the v1 Mantis small but complete: connecting, identifying the device, reading it, clamping the plate and reading the chip tags, on any v3 or v4. Later PRs build the arm, the chips and dispensing on top. The v1 device structure follows the STAR and CLARIOstar v1 drivers.

Every Mantis is calibrated at the factory: its clamp levels and RFID antennas sit in that unit's own files, installed with the vendor software. A driver that hard-codes one unit's values runs every other unit on the wrong ones.

PR Content

  • Mantis(...) builds the v1 device: MantisDevice with MantisDriver on the FTDI FT2232H's two interfaces (B carries commands, A carries the RFID reader), plus a 3D resource model with the left and right chip racks.
  • Setup reads the device: firmware per board, generation (v3 or v4, from which boards answer), and motor limits. Firmware sets confirmed for a generation pass silently; any other set logs a warning.
  • Per-unit calibration:
    • oem_config_directory: the unit's vendor Data/Device/Configs folder. The clamp levels come from Device.config, the antennas from RFID.config.
    • save_configuration(path=...) saves the device and those unit values to a JSON, so the folder is needed only once. Later setups take that file as declared_configuration_json.
    • Both given: they are cross-checked, and setup refuses on any difference, listing every one.
    • Neither given: the generation's recording is used, with a warning that these are another unit's values.
  • mantis.plate_clamp (v4, the motorised clamp): lock(level=...), unlock(), request_state(). lock() without a level uses default_lock_level (1, the weakest), a public default like the v1 STAR's default_y_speed. Lock and unlock poll until done and raise PlateClampError at once on a fault state. stop() releases a clamp left locked.
  • mantis.rfid: request_tag(slot=...) and request_tags() read the chip in each input slot: serial, chip type, manufacturing date and whether the tag is valid. An empty slot gives None.
  • Errors: every device error code, grouped by kind under MantisError, e.g. CommandRefusedError, PlateClampError, MantisTimeoutError. MantisConfigurationError subclasses ValueError.
  • MantisLiquidClass and mantis_mapping: the 26 v4 liquid classes as code, the same pattern as the v1 STAR liquid classes. Nothing uses them until dispensing lands; they're infrastructure.
  • Simulation: Mantis(simulation=True) sits above the wire and answers from a declared configuration: your own saved file, or the v4 recording when none is given.
  • Docs: a hello-world notebook and the device registry entry.
  • Tests: the frame format and check word, the wire replies recorded from a v4, discovery, the cross-check and its refusals, the vendor-file parser on v3 and v4 files, the clamp's states, faults and default level, RFID tag decoding and validity, and the liquid-class mapping.

Usage

  1. The first setup reads the unit's calibration from the vendor folder; save it, and later setups need only the file
from pylabrobot.formulatrix import Mantis

mantis = Mantis(name="mantis", oem_config_directory="path/to/Data/Device/Configs")
await mantis.setup()

mantis.driver.configuration.generation  # 'v4'
mantis.driver.configuration.firmware_versions  # {0: '4.20.6.2', 10: '1.0.8', 11: '0.1.7 DBG', 12: '0.1.8 DBG'}
mantis.driver.save_configuration(path="my_mantis.json")
await mantis.stop()

mantis = Mantis(name="mantis", declared_configuration_json="my_mantis.json")
await mantis.setup()
  1. Clamp the plate, at the default level or a chosen one, then release it
await mantis.plate_clamp.lock()  # default_lock_level, 1
mantis.plate_clamp.default_lock_level = 2
await mantis.plate_clamp.lock()  # level 2
await mantis.plate_clamp.request_state()  # 3, clamping done
await mantis.plate_clamp.unlock()
  1. Read which chip sits in each input slot
tags = await mantis.rfid.request_tags()
tags[1]["chip_type"], tags[1]["valid"]  # ('low_volume_hi_fi', True)
tags[3]  # None: empty slot
  1. Without a Mantis
# your own unit, from the file save_configuration wrote
simulated = Mantis(name="simulated_mantis", simulation=True, declared_configuration_json="my_mantis.json")
await simulated.setup()

# no file: the v4 recording, another unit's values
simulated = Mantis(name="simulated_mantis", simulation=True)

v3

No v3 has run this code yet. On a v3, setup should connect, identify the generation and read the device. The v3 has no RFID.config, so RFID reads refuse. Its pneumatic clamp isn't in this PR: #987 and the vendor files disagree on its command numbers, and we're waiting on a capture to settle them. If you have a v3, a run of the notebook's first cells would tell us a lot.

On the device

A Mantis v4, every step on macOS (libftdi) and on Windows (FTDI's own D2XX driver, follow-up PR), with identical bytes on both:

result
setup v4, firmware confirmed
lock, level 1 / 2 / 3 clamping done after 2.21 / 1.18 / 0.67 s
lock() at default_lock_level 1 / 2 2.20 / 1.18 s, the same lock frames as an explicit level
unlock released after 0.20 s
request_tags both chips read and valid; empty slots None
declared file, agreeing / claiming v3 connects / refused, naming the generation
no folder, no file connects, with the warning

🤖 Generated with Claude Code

@BioCam
BioCam requested a review from a team as a code owner November 11, 2026 17:38
@BioCam
BioCam force-pushed the mantis-v1-connect-clamp branch 2 times, most recently from cbebef1 to 8e6e57a Compare November 11, 2026 18:22
…read the chip tags

- the link on the FTDI's interface B: framing and check word, one command in flight, replies matched on packet number and board, events to waiters registered before the command is sent; a negative status is a refusal and raises `CommandRefusedError` with the device's message
- discovery reads each board's firmware: only board 0 must answer, board 11 answering means v4
- reads for firmware version, name and motor limits
- the declared configuration check, a declared board that does not answer refuses setup, and confirmed firmware for the v4 set seen on a device
- the unit's own values: read from its vendor folder (`oem_config_directory`) or a saved configuration, which must agree where both hold a value; else the generation's recording, with a warning; `save_configuration` keeps the unit's values so the folder is needed once
- recordings of a v4 and a v3
- errors by kind under `MantisError`, each also its built-in, with tables for the motor, sensor alarm and clamp state codes
- the plate clamp on the LC3 board: `lock(*, level)` at the unit's levels waits for clamping done, raises at once on a failure state and releases on failure; `unlock()`, `request_state()`; stop releases a clamp not reading unclamping done
- the RFID reader on interface A: `request_tag(*, slot)`, `request_tags()`; the antenna switch through WritePPI at the multiplexer's address; built only when the reader answers
- `MantisLiquidClass` and `mantis_mapping`, the v4 liquid classes by viscosity band and chip type
- `MantisDevice` with the `Mantis()` factory, `mantis.plate_clamp` and `mantis.rfid`
- simulation above the wire, from a recording
- the resource model in mm: the main body, the plate holder and both chip racks
- design.md, the hello-world notebook and the registry entry

Co-Authored-By: xbtu2 <228601186+xbtu2@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@BioCam
BioCam force-pushed the mantis-v1-connect-clamp branch from 8e6e57a to 735e9dd Compare November 11, 2026 18:31

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant