Skip to content

Air liha backend#981

Open
Robert-Keyser-Calico wants to merge 16 commits intoPyLabRobot:mainfrom
Robert-Keyser-Calico:air-liha-backend
Open

Air liha backend#981
Robert-Keyser-Calico wants to merge 16 commits intoPyLabRobot:mainfrom
Robert-Keyser-Calico:air-liha-backend

Conversation

@Robert-Keyser-Calico
Copy link
Copy Markdown
Contributor

This pull request adds comprehensive support for the Tecan Air LiHa (ZaapMotion) hardware variant to the pylabrobot project. It introduces a new backend subclass, updates liquid class mappings, and provides detailed documentation and tooling for reverse engineering and testing. The changes are organized into implementation planning, technical investigation, new utilities for USB protocol decoding, and a demo script for a related bulk dispenser instrument.

Air LiHa Support Implementation:

  • Added a detailed implementation plan for supporting Air LiHa via a new AirEVOBackend subclass, outlining architectural decisions, required overrides, ZaapMotion configuration, and test coverage. This ensures clean separation from the existing syringe-based backend and minimizes risk to current users.
  • Documented the technical investigation into Air LiHa, including reverse engineering of ZaapMotion initialization, motor configuration, plunger conversion factors, per-operation command patterns, and liquid class data extraction from EVOware XML files.

Developer Tooling and Utilities:

  • Added decode_usb_capture.py, a utility script for decoding Tecan EVO USB captures from Wireshark, which assists in analyzing and validating firmware-level communication during development and troubleshooting.

Demo and Testing:

  • Added demo_multidrop.py, a demo script showcasing usage of the Multidrop Combi bulk dispenser, including plate configuration, dispensing, and error handling, to aid both development and user onboarding for related instruments.

These changes collectively provide a robust foundation for Air LiHa support, facilitate future hardware integrations, and improve developer productivity with new reverse engineering tools.

Robert-Keyser-Calico and others added 14 commits March 27, 2026 14:12
The Air LiHa uses ZaapMotion BLDC motor controllers for air displacement
pipetting. After power cycle, these controllers boot into bootloader mode
and require motor configuration (PID gains, current limits, encoder setup)
before Z-axis homing can succeed. This was reverse-engineered from USB
captures of EVOware's initialization sequence.

Changes:
- New AirEVOBackend subclass with ZaapMotion boot exit and motor config
- Air LiHa plunger conversion factors (106.4 steps/uL, 213 speed factor)
- ZaapMotion force mode (SFR/SFP/SDP) wrapping around plunger operations
- ZaapDiTi liquid class entries for Water and DMSO with DiTi 50uL tips
- Hardware test scripts and investigation documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The ZaapMotion SDO config command must be sent right before PIA
(matching EVOware's sequence). Also add logging and post-failure
diagnostics to the init test script.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move init-skip logic into AirEVOBackend.setup() (_is_initialized,
  _setup_quick, _setup_full) with fast 1s buffer drain
- Fix labware_library.py: use TecanPlate for Eppendorf plate (required
  by aspirate/dispense), correct tip length to 58.1mm (measured),
  set tip_type to AIRDITI, fix TipSpot import
- Z-coordinate issue identified: tip pickup Z is ~100mm too high.
  Root cause is _liha_positions adding carrier offset + tip_length
  to z_start values that are already absolute Tecan coordinates
  (0=top, z_range=deck). Needs coordinate transform fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix pick_up_tips to use tip rack z_start directly instead of
  broken _liha_positions Z calculation (coordinate system mismatch)
- Fix Y-spacing (ys) in aspirate/dispense: use plate item_dy (well pitch)
  instead of well size (was 54 instead of 90, causing invalid operand)
- Add jog_liha.py interactive teaching tool for X/Y/Z positions
- Add tips_off.py emergency tip removal script
- Add temporary X offset (+60 = 6mm) for tip pickup alignment
- Aspirate Z still needs calibration (z_start too high)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
16 tests covering all Air LiHa-specific logic with mocked USB:

- ConversionFactorTests: verify 106.4 steps/uL and 213 speed factor
  are used in airgap calculations (not syringe 3/6)
- ForceModeSFRTests: verify SFR/SFP/SDP commands sent to all 8 tips
  for force_on and force_off
- InitSkipTests: verify REE0 parsing for init-skip logic
  (@ = OK, A = init failed, G = not init, Y = tip not fetched)
- ZaapMotionConfigTests: verify 33-command config sequence,
  RCS skip logic, safety module SPN/SPS3
- PickUpTipsAirTests: verify tip pickup uses Air LiHa conversion
  factors (SEP=14910, PPR=1064)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mirror of v1b1 firmware features for the legacy architecture:
- Wrap raw send_command calls (REE, PIA, PIB, BMX, BMA, PPA, SDT) in typed EVOArm/LiHa methods
- Add new firmware commands: RPP, RVZ, RTS, PAZ for plunger/Z/tip queries
- Create inline ZaapMotion class replacing raw T2{tip} string commands
- Implement mixing (_perform_mix) and blow-out (_perform_blow_out) with Air LiHa force mode overrides
- Add request_tip_presence() via RTS firmware command
- Add post-grip plate verification, configurable RoMa park position
- Update tests for new firmware wrapper call patterns

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…, reformat

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix mypy errors: add type annotations for z_aspirate list, type: ignore
  for mock method assignments, use mock_send variable for mock attribute access
- Fix ruff import sorting in air_evo_tests.py
- Fix item_dy access on Optional[Resource] with None check
- Add STEPS_PER_UL/SPEED_FACTOR constants to EVOBackend base class
- Update _typos.toml: exclude OCR'd manual text and USB capture logs,
  whitelist Tecan firmware command abbreviations (ALO, SOM, SHS, etc.)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Files kept locally but removed from repo:
- keyser-testing/Tecan Manuals/ (PDFs + OCR text extractions)
- keyser-testing/evoware-usb-capture-*/ (pcap, trace logs)
- keyser-testing/evoware-pktmon-capture-*/ (etl, decoded logs)
- keyser-testing/*.pdf (ZaapMotion docs)

Also:
- Add these paths to .gitignore
- Fix ruff import sorting in air_evo_backend.py

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix tip drop: use SDT mode=0 (above rack) instead of mode=1 to prevent
  crashes on taller tip racks (1000uL). Position at tip rack z_start before eject.
- Add fitting_depth tip extension to aspirate/dispense Z calculations.
  Z target = plate.z_start/z_dispense + (tip_length - fitting_depth) * 10
- Detect unresponsive ZaapMotion controllers during init and raise clear error
  instead of 264 silent warnings followed by a crash.
- Home LiHa before RoMa PIA to prevent collision during full init sequence.
- All fixes validated on hardware with 50/200/1000uL tips.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Robert-Keyser-Calico
Copy link
Copy Markdown
Contributor Author

Air LiHa Hardware Validation — Tecan EVO

Summary

Complete hardware validation of the Air LiHa (ZaapMotion) backend on a Tecan EVO 150 with 8 channels. All operations tested with 50uL, 200uL, and 1000uL disposable tips.

What was validated

  • Init: Cold boot (ZaapMotion boot exit + config + PIA) and warm reconnect (~3s)
  • Tip handling: Pickup and drop for all 3 tip sizes
  • Pipetting: Aspirate and dispense with LLD (liquid level detection)
  • RoMa plate handling: Pick/place across 6 carrier positions with move_resource() API

Key fixes from hardware testing

  • Tip drop crash on tall racks: AST with SDT mode=1 pushes past the rack surface on 1000uL boxes. Fixed to use mode=0 (eject at current Z) after positioning at tip rack z_start.
  • Accurate tip extension: Measured fitting_depth=11.0mm on hardware (consistent across all tip sizes). Aspirate/dispense Z targets now use plate.z_start + (tip_length - fitting_depth) * 10 instead of raw plate Z.
  • Init collision safety: LiHa now homes before RoMa PIA to prevent arm collision.
  • ZaapMotion error detection: Raises a clear "power cycle required" error when controllers are unresponsive, instead of 264 silent warnings.

Tip dimensions (measured with calipers + on-instrument Z comparison)

Tip Length Fitting depth Extension
50uL 58.0mm 11.0mm 47.0mm (470 units)
200uL 58.5mm 11.0mm 47.5mm (475 units)
1000uL 96.1mm 11.0mm 85.1mm (851 units)

RoMa calibration

  • Calibrated roma_x (1878→1670) and roma_y (423→380) from taught positions
  • Added TecanGripperArm(GripperArm) subclass to route move_resource() through carrier-based backend methods (v1b1 only)

@Robert-Keyser-Calico Robert-Keyser-Calico marked this pull request as ready for review April 9, 2026 22:44
Robert-Keyser-Calico and others added 2 commits April 9, 2026 16:12
Hardware testing scripts, calibration data, and investigation notes
are kept on the keyser-combined dev branch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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