LevyLab Guide¶
The LevyLab ecosystem connects FLEX to the lab's LabVIEW Instrument
Framework (IF): each instrument runs as an IF app exposing a JSON-RPC API
over ZMQ, experiments are configured in the Configure Experiments VI, data
goes to TDMS + Nextcloud, metadata to the lab PostgreSQL, and Asana gets a
task per experiment via flex-asana — the Asana API directly, no n8n.
Setup (once per machine)¶
irm flex.levylab.org/install.ps1 | iex
flex ecosystem use levylab
$env:NEXTCLOUD_PASSWORD = '...' # or add to the machine environment
$env:ASANA_ACCESS_TOKEN = '...'
$env:ASANA_EXPERIMENTS_PROJECT_GID = '...'
CESession¶
CESession is an Experiment bootstrapped from the Configure Experiments VI:
it reads Control Experiment.json, connects a driver to every configured IF
app (matched by LabVIEW class), and attaches them by their CE type.
from flex import CESession, Scan, sweep
import numpy as np
with CESession() as exp: # user/device/wiring from the VI
exp.DAQ.set_ao_dc(1, 0.0)
print(exp.wiring) # {lockin ch: (electrode, label)}
gate = exp.DAQ.parameters.get("ao1_dc") or (lambda v: exp.DAQ.set_ao_dc(1, v))
Scan(sweep(gate, np.linspace(0, 1, 101), delay=0.1)) \
.measure(results=exp.DAQ.get_results) \
.on_abort(lambda: exp.DAQ.set_ao_dc(1, 0.0)) \
.run(exp, name="gate sweep")
Everything from the base Experiment applies: measurements write TDMS files
(per the levylab config), records land in PostgreSQL, exp.update() picks up
instruments added in the VI while running, and an Asana task tracks the run
from start to end (see Experiments & data).
Talking to IF apps directly¶
Every IF driver is a ZMQInstrument; drivers are thin, and raw access is
always available:
from flex_drivers.levylab.lockin import Lockin
with Lockin("lockin", "tcp://localhost:29170") as li:
li.set_ao_amplitude(1, 0.1) # sends setAO_Amplitude
print(li.get_results())
print(li.help()) # the IF app's own method list
li.call("setSweep", {...}) # any method, verbatim
Errors from the IF (error in the JSON-RPC reply) raise
ZMQInstrumentError; a dead app raises TimeoutError and the connection
recovers on the next call.
Non-IF instruments¶
Nothing requires the Instrument Framework: a plain VISA/serial instrument
(e.g. flex_drivers.srs.SR7270) registers into the same session next to IF
drivers:
with CESession() as exp:
exp.add(SR7270, "lockin2", serial="12345")
Testing drivers without LabVIEW¶
flex_protocols.testing.FakeIFServer is an in-process fake IF app:
from flex_protocols.testing import FakeIFServer
with FakeIFServer({"getResults": {"X": [1.0]}}) as server:
li = Lockin("li", server.address)
assert li.get_results() == {"X": [1.0]}
The historical database¶
v2 writes to its own PostgreSQL tables (experiments, measurements,
notes). The v1 tables (exp, meas, cell_log) are untouched; migrating
them is a planned one-off — see the migration notes.