Skip to content

About

REST/JSON API for the PerkinElmer Wallac Victor2 (1420) multimode plate reader — start assays, stream live status, and export results over HTTP, driven from Linux via the OEM stack in a Windows 7 VM.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

wallac-victor2-api

License Python CI CodeQL

A REST/JSON API for the PerkinElmer Wallac Victor2 (1420) multimode microplate reader — start assays, stream live status, and pull results over HTTP, driven from Linux.

The original instrument software exposes only a 2005-vintage Windows GUI with no automatable interface. This project runs the unmodified OEM software inside a Windows 7 VM and wraps its COM automation server behind a clean HTTP API, so a used Victor2 / 1420 can drop straight into a Linux/robotics screening pipeline (TR-fluorescence, prompt fluorescence, absorbance/ photometry, luminescence, fluorescence polarization).

A second stack — the bridge — runs on the Linux host and integrates the instrument with eLabFTW (electronic lab notebook): a Run Builder UI for protocol authoring, a direct-submit job API, result write-back, and live status streaming.

Scope: this repository contains the instrument API microservice (vm-agent/), the eLabFTW bridge (bridge/), and documentation. It does not contain or redistribute any PerkinElmer / Wallac software, firmware, or installation media — you must supply a legitimately licensed OEM installation in the VM.

Deployment scope: the bridge and designer services are designed for deployment on a trusted local area network or Tailscale network and must not be exposed to the public internet without additional hardening. See SECURITY.md for the threat model and the steps required to deploy beyond a trusted LAN.

Demo

Wallac Victor2 API and run-builder UI demonstration

The HTTP API driving the Victor2 / 1420 from Linux, with the run-builder UI assembling a protocol and streaming live results back over SSE.

Architecture

Two stacks glue together over the libvirt NAT: the vm-agent (Python 3.8 + comtypes) runs inside the Windows 7 VM and drives the OEM COM automation server (Wallac1420.Server) behind a REST/JSON + SSE interface; the bridge (FastAPI, on the Linux host) sits between the Run Builder UI and the vm-agent, integrating with eLabFTW as the durable archive while submitting and executing jobs directly over HTTP.

  Run Builder ──► designer :8422 ──► eLabFTW (drafts, signed specs)
                                   bridge   :8423 ──► eLabFTW (experiment + results)
                                   vm-agent :8420 ──► OEM COM ──► Victor2 / 1420

For the full architecture (component breakdown, threading model, key design decisions) see docs/architecture.md; for the direct-submit design rationale see docs/architecture-direct-submit.md.

API

vm-agent API — :8420

Method & path Purpose
POST /measure one call: run a protocol by name, wait, return the OD table
GET /docs self-describing catalog of every endpoint
GET /health liveness + ready flag + instrument connection state
GET /status latest cached instrument snapshot (from the monitor)
GET /monitor SSE real-time state stream (~1 Hz)
GET /instrument model, serial, technologies, temperature
GET /protocols assay protocols (?q= to search, ?refresh=1 to reload)
GET /protocols/{name|id} resolve one protocol by name or id
POST /runs start an assay ({"protocol": "<name|id>"}; "dry_run": true)
GET /runs / GET /runs/{id} run list / single run state
GET /runs/{id}/results results — live while running, persisted once measured
GET /runs/{id}/export results as CSV (?shape=grid, ?value=od|raw)
POST /runs/{id}/abort cancel a run (guarded: only ≥60 s in)
DELETE /runs/{id} drop a finished/failed/stuck run record (?force=1)
GET /jobs / GET /jobs/{id} completed assays
GET /jobs/{id}/results per-well results (JSON, deduped; ?dedup=0 for raw rows)
GET /jobs/{id}/export CSV — ?format=long|grid, ?value=raw|od
POST /admin/reconnect re-establish the COM link

The friendly entrypoint is POST /measure: give it a protocol name, it resolves the id, starts the run, waits for the plate read, and returns the deduped per-well OD table (optionally an 8×12 grid). Errors are actionable — e.g. 409 instrument_not_ready with a hint telling you to close the lid — rather than raw COM tracebacks.

bridge / designer API — :8423 / :8422

The bridge (job submit / execute / abort) and designer (Run Builder, draft finalize / clone) expose their own REST endpoints. Tables and the shared auth model live in docs/bridge-api.md.

Auth (summary): all three services use optional Authorization: Bearer <token>. The vm-agent reads its token from a file on the VM (TOKEN_FILE in agent.py); the bridge and designer read from env vars (WALLAC_BRIDGE_TOKEN, WALLAC_DESIGNER_TOKEN). The vm-agent logs a warning when its token file is absent; bridge/designer disable auth with no warning when their env token is unset. Tokens must not be committed to this repository; tracked templates contain placeholder values. See docs/auth-secrets-policy.md.

Full request/response details: docs/api-reference.md and docs/bridge-api.md.

Install & use

Prerequisites

  • A Linux/QEMU host running a Windows 7 SP1 guest on the libvirt NAT.
  • A licensed Wallac 1420 / Victor2 OEM installation inside the guest, with the reader connected and recognized by the OEM stack (i.e. MlrMgr shows the instrument online).
  • In the guest: Python 3.8 (32-bit) + comtypes (the OEM COM server and DAO are 32-bit).
  • On the host: Python 3.11+ + uv (for tooling) and the bridge dependencies (fastapi, uvicorn, pydantic).

Deploy the vm-agent (in the VM)

  1. Copy vm-agent/ into the guest (e.g. C:\install\).
  2. (Optional auth) write a secret token to the path named by TOKEN_FILE in agent.py (default C:\Users\Public\agent_token.txt).
  3. Enable autologon for the desktop user and put start-stack.bat in that user's Startup folder. On boot it launches the OEM GUI, the lid watcher, and the agent.

On boot the agent listens on the guest's libvirt NAT address, port 8420.

Deploy the bridge (on the Linux host)

  1. Copy deploy/bridge.env.example to /etc/wallac-bridge/bridge.env and fill in the eLabFTW URL + API key, vm-agent URL + token, and optional designer/bridge tokens. For browser/Run Builder deployments, set WALLAC_CORS_ORIGINS to the exact Run Builder origin (default http://localhost:8422) — the designer at :8422 makes cross-origin requests to the bridge at :8423 and the browser enforces CORS.

  2. Install the systemd services:

    sudo cp deploy/wallac-bridge.service  /etc/systemd/system/
    sudo cp deploy/wallac-designer.service /etc/systemd/system/
    sudo systemctl daemon-reload
    sudo systemctl enable --now wallac-bridge wallac-designer

The bridge listens on :8423, the designer/Run Builder on :8422.

Use — from the Linux host (replace the IP with your guest's NAT address; H is the optional bearer header):

H='Authorization: Bearer <token>'
VM=192.168.122.203:8420        # your Windows 7 guest on the libvirt NAT

curl -H "$H" "http://$VM/health"
curl -H "$H" "http://$VM/protocols?q=absorb"          # search protocols by name

# the easy path — run a protocol by NAME, wait, get the OD table back:
curl -H "$H" -H 'Content-Type: application/json' \
     -d '{"protocol":"Absorbance @ 600"}' "http://$VM/measure"

# ...or drive it yourself: start without waiting, poll, then fetch results:
curl -H "$H" -H 'Content-Type: application/json' \
     -d '{"protocol":"Absorbance @ 600","wait":false}' "http://$VM/measure"
curl -H "$H" "http://$VM/runs/<run_id>"
curl -H "$H" "http://$VM/runs/<run_id>/results?shape=grid&value=od"

curl -N -H "$H" "http://$VM/monitor"     # live state (SSE)

# any past run's results as an 8x12 grid of computed absorbance:
curl -H "$H" "http://$VM/jobs/<id>/export?format=grid&value=od"

Operations & internals

The operational runbook (start / verify / restart the win7-wallac VM, the ARCnet / VFIO passthrough setup) and environment-specific deployment gotchas — including the nested-Wallac\ OEM install path — live in docs/deployment-notes.md. The bench-level runbook is in the internal sister repo wallac-victor2-linux (host-config/VM-OPERATIONS.md).

Development

Quality gates apply to both maintained stacks — the vm-agent (vm-agent/{agent,lid_watcher,launch_as_user}.py) and the bridge (bridge/*.py). Tools run via uv, so no global installs are needed:

make setup       # create/update the locked project environment
make validate    # ruff + pyright + format-check + pytest (bridge cov >=80%) + complexipy (max 15/func)
make format      # auto-fix lint + format
make test        # fast suite — unit tests only
make coverage    # pytest with the bridge coverage gate (>=80%)
make complexity  # cognitive complexity check (max 15 per function)
make setup_dev   # create environment + install pre-commit, commit-msg, and pre-push hooks

The unit tests cover pure data-shaping helpers in both stacks: vm-agent parsing, OD computation, plate-grid CSV; bridge analysis, canonical schemas, designer CRUD, executor, job manager, and config. Hypothesis drives property-based tests for canonicalization, well-name normalization, dedup-key stability, and WellSpec round-trip invariants. The bridge coverage metric intentionally excludes bridge/elabftw.py and bridge/vm_agent_client.py — those volatile HTTP adapters are exercised by hardware/integration testing, not Linux unit tests. The COM/HTTP paths in the vm-agent require Windows, comtypes, and a live instrument.

Documentation

Authority What it covers
README Entry point — overview, quick-start, API summary, this index
Architecture System design, vm-agent/bridge components, threading model
Direct-submit architecture Direct-submit decision, role changes vs the legacy polling model
Bridge & designer API :8423 / :8422 endpoints and auth model
vm-agent API reference Full :8420 request/response reference
Abort & recovery Abort state machine, race guarantees, incident/rollback
eLabFTW object model Signed object types, canonical JSON, signing model
Auth & secrets policy Service identity, token handling, strict-auth mode
Security Threat model, hardening steps, vulnerability reporting
Deployment notes OEM install path gotchas, VM-operations pointer
Protocol authoring plan Generated protocol design + spec contract
Stage-7 hardware e2e plan Manual integration test plan for the instrument
Agent learnings Dev-only gotchas and patterns (not user-facing)

License

Licensed under the Apache License, Version 2.0 — see LICENSE and NOTICE. This project controls, but does not include or redistribute, any PerkinElmer / Wallac instrument software, firmware, or installation media; supply your own legitimately licensed OEM installation.

About

REST/JSON API for the PerkinElmer Wallac Victor2 (1420) multimode plate reader — start assays, stream live status, and export results over HTTP, driven from Linux via the OEM stack in a Windows 7 VM.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages