Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

PyLabDrivers logo

PyLabDrivers

Python drivers for remote control of electronic chemistry lab instruments

Python 3.8+ MIT License 20 Drivers Tests Passing Platform

Installation • Supported Devices • API Reference • Tests • Contributing


Overview

PyLabDrivers provides Python drivers to interface with a variety of chemistry lab instruments. Each driver is implemented as a separate module under the pylabdrivers package and comes with examples and unit tests. The package supports serial, analog (NI-DAQmx), Modbus TCP, and FTDI communication protocols.

Key Features

  • 20 instrument drivers — mass flow controllers, pumps, sensors, valves, fraction collectors, autosamplers, chillers, and more
  • Object-oriented design — consistent interface patterns across all drivers
  • Tested — unit tests with mocked hardware for every driver
  • Documented — standalone example scripts for each device

Supported Devices

# Device Communication
1 Alicat Mass Flow Controller Serial (RS-232)
2 Equilibar Pressure Controller Serial (RS-232)
3 FloDx Pressure Sensor Serial (RS-232)
4 Harvard PHD Ultra Syringe Pump Serial (RS-232)
5 Knauer Azura Pump (Analog) NI-DAQmx
6 Knauer Azura Pump (Serial) Serial (RS-232)
7 Masterflex Pump NI-DAQmx
8 MilliGAT Positive Displacement Pump Serial (RS-232)
9 Teledyne ISCO Syringe Pump (D-Series) Serial (RS-232)
10 Teledyne ReaXus M1 Class Pump Serial (RS-232)
11 VICI Valco Selector/Injector Valve Serial (RS-232)
12 Watlow F4T Temperature Controller Modbus TCP
13 Brooks SLA5800 Series MFC Serial (S-Protocol)
14 Omega DP9800 Temperature Monitor Serial (RS-232)
15 BK Precision 9200 Power Supply Serial (VISA)
16 Heidolph Overhead Stirrer Serial (RS-232)
17 Keller LEX1 Pressure Sensor Serial (RS-232)
18 Teledyne Foxy R1/R2 Fraction Collector Serial (RS-232)
19 SiELC Alltesta Mini Autosampler FTDI USB-Serial
20 Huber Pilot ONE Laboratory Chiller Serial (RS-232)

Installation

# Clone the repository
git clone https://github.com/MSDLLCpapers/PyLabDrivers.git
cd PyLabDrivers

# Install the package
pip install .

# Or install in development mode
pip install -e .

# Install dependencies only
pip install -r requirements.txt

Requirements

  • Python 3.8 or higher
  • Dependencies listed in requirements.txt
  • Huber Pilot ONE driver additionally requires pySoftcheck, which is distributed by Huber and is not included in this repository or on PyPI. Download the pySoftcheck wheel from Huber, then install it:
    pip install pysoftcheck-3.0.0-py3-none-any.whl

Configuration

Device settings (ports, baud rates, etc.) are stored in config/config.yaml. Update this file with your hardware configuration before running examples:

devices:
  alicatmfc:
    port: "COM5"
    unit_id: "A"
    baudrate: 19200

  huber_pilotone:
    port: "COM16"
    baudrate: 9600
    address: 1

  foxy_teledyne:
    port: "COM12"
    baudrate: 9600
    model: "R2"

  sielc_alltesta:
    port: "COM13"

Load configuration in your scripts:

from config.config import load_config
config = load_config()

Drivers and Methods

Alicat MFC

Method Description
connect() Open serial connection
disconnect() Close serial connection
set_setpoint(value) Set flow setpoint
get_status() Get actual flow rate
poll_data() Poll current measurements
get_available_gases() List available gases
set_gas(gas_id) Set gas type
from pylabdrivers.alicatmfc import AlicatMFC, AlicatError
import time

mfc = AlicatMFC(port="COM5", unit_id="A", baudrate=19200)
mfc.connect()
mfc.set_setpoint(1.0)
time.sleep(2)
print("Flow rate:", mfc.get_status())
mfc.disconnect()

Azura Knauer Pump (Analog)

Method Description
set_flowrate(ml_per_min) Set flow rate via analog voltage
start_pump() Start pump (digital output)
stop_pump() Stop pump
close() Close NI-DAQmx tasks
from pylabdrivers.azuraknauer_analog import KnauerAzuraPump
import time

pump = KnauerAzuraPump(
    device_name="Dev1", ttl_port=0, ttl_line=0,
    analog_out_channel="Dev1/ao0", pump_head_type='10ml'
)
pump.set_flowrate(2.0)
pump.start_pump()
time.sleep(5)
pump.stop_pump()
pump.close()

Azura Knauer Pump (Serial)

Method Description
connect() / disconnect() Manage serial connection
set_flow(value) Set flow rate (uL/min)
get_flow() Read current flow rate
get_pressure() Read pump pressure
set_head_type(head_type) Set pump-head type (10 or 50 mL)
set_remote() / set_local() Toggle control mode
start() / stop() Start or stop the pump
from pylabdrivers.azuraknauer_serial import AzuraKnauerPump, AzuraKnauerPumpError
import time

pump = AzuraKnauerPump(port="COM16", xonxoff=True)
pump.connect()
pump.set_remote()
pump.set_head_type(50)
pump.set_flow(25000)
pump.start()
time.sleep(5)
print(f"Flow: {pump.get_flow()} uL/min")
pump.stop()
pump.set_local()
pump.disconnect()

Equilibar Pressure Controller

Method Description
connect() / disconnect() Manage serial connection
set_setpoint(value) Set pressure setpoint
get_response() Poll device response
get_pressure(response) Extract pressure from response
set_streaming_mode() / set_polling_mode() Toggle modes
from pylabdrivers.equilibarpc import Equilibar, EquilibarError
import time

eq = Equilibar(port="COM5", unit_id="A", baudrate=19200)
eq.connect()
eq.set_setpoint(50.0)
time.sleep(1)
response = eq.get_response()
print("Pressure:", response)
eq.disconnect()

FloDx Pressure Sensor

Method Description
connect() / disconnect() Manage serial connection
read_pressure() Read pressure value
from pylabdrivers.flodx import FloDxPressureSensor, FloDxError

sensor = FloDxPressureSensor(port="COM7")
sensor.connect()
print("Pressure:", sensor.read_pressure())
sensor.disconnect()

Harvard Syringe Pump

Method Description
connect() / disconnect() Manage serial connection
set_syringe_diameter(diameter) Configure syringe
run_infuse() Start infusion
get_current_rate() Read flow rate
stop_pump() Stop pump
from pylabdrivers.harvard import HarvardSyringePump, HarvardError
import time

pump = HarvardSyringePump(port="COM3", address=0)
pump.connect()
pump.set_syringe_diameter(10.0)
pump.run_infuse()
time.sleep(5)
print("Rate:", pump.get_current_rate())
pump.stop_pump()
pump.disconnect()

Masterflex Pump

Method Description
connect() / disconnect() Manage NI-DAQmx tasks
set_flowrate(flowrate) Set analog output for flow
run_pump() Start pump
stop_pump() Stop pump
from pylabdrivers.masterflex import Masterflex, MasterflexConnectionError
import time

pump = Masterflex(ao_channel="Dev1/ao0", dio_channel="Dev1/port0/line0", max_flowrate=1000)
pump.connect()
pump.set_flowrate(500.0)
pump.run_pump()
time.sleep(10)
pump.stop_pump()
pump.disconnect()

MilliGAT Pump

Method Description
connect() / disconnect() Manage serial connection
set_flowrate_continuous(flowrate) Run pump at flow rate
set_volume_continuous_aspirate(...) Aspirate a volume
set_volume_continuous_dispense(...) Dispense a volume
stop() Stop pump
from pylabdrivers.milliGAT import MilliGAT
import time

pump = MilliGAT(port="COM4", device_name="A", EU=13430)
pump.connect()
pump.set_flowrate_continuous(5)  # 5 mL/min
time.sleep(10)
pump.stop()
pump.disconnect()

Teledyne ISCO Syringe Pump

Method Description
connect() / disconnect() Manage serial connection
initialize() Set remote control mode
set_units(flow_unit, pressure_unit) Configure units
set_flowrate(value) Set flow rate
set_pressure(value) Set pressure
run() / stop() Start or stop pump
from pylabdrivers.teledyneisco import TeledyneISCO, TeledyneISCOError
import time

pump = TeledyneISCO(port="COM5", unit_id=6)
pump.connect()
pump.initialize()
pump.set_units("ML/MIN", "PSI")
pump.set_flowrate(5.0)
pump.run()
time.sleep(5)
print("Flow:", pump.get_flowrate())
pump.stop()
pump.disconnect()

Teledyne ReaXus M1 Class Pump

Method Description
connect() / disconnect() Manage serial connection
write_command(command) Send command (no response)
query_command(command) Send command and read response
get_current_conditions() Read pressure and flow
get_status() Read detailed pump status
clear_faults() Clear fault conditions
from pylabdrivers.teledynereaxus import TeledyneM1Reaxus, TeledyneM1ReaxusError

pump = TeledyneM1Reaxus(port="COM7")
pump.connect()
print("ID:", pump.get_id())
pump.write_command("FI05000")
print("Conditions:", pump.get_current_conditions())
pump.disconnect()

VICI Valco Selector/Injector Valve

Method Description
connect() / disconnect() Manage serial connection
switch(position) Move to position
get_status() Read status
get_current_position() Read position
set_number_of_positions(n) Configure valve
from pylabdrivers.vicivalco import VICI, ValcoValveError
import time

valve = VICI(port="COM6")
valve.connect()
valve.switch(1)
time.sleep(2)
print("Position:", valve.get_current_position())
valve.switch(2)
valve.disconnect()

Watlow F4T Temperature Controller

Method Description
connect() / disconnect() Manage Modbus TCP connection
write_temperature_setpoint(loop, celsius) Set temperature
read_temperature_setpoint(loop) Read setpoint
read_actual_temperature(loop) Read actual temperature
from pylabdrivers.watlowf4t import WatlowF4TController

controller = WatlowF4TController(host="192.168.0.220", port=502)
controller.connect()
controller.write_temperature_setpoint(loop=1, celsius_setpoint=25.0)
print("Setpoint:", controller.read_temperature_setpoint(loop=1), "C")
print("Actual:", controller.read_actual_temperature(loop=1), "C")
controller.disconnect()

Brooks SLA5800 Series MFC

Method Description
connect() / disconnect() Manage serial connection
read_flow() Read current flow (mL/min)
set_flow(flowrate) Set flow setpoint
read_setpoint() Read current setpoint
read_flow_range(select_code) Read flow range
from pylabdrivers.brooksmfc_SLA5800 import BrooksMFC, BrooksError

mfc = BrooksMFC(tag="62100125", port="COM9")
mfc.connect()
print("Flow:", mfc.read_flow(), "mL/min")
mfc.set_flow(100)
mfc.disconnect()

Omega DP9800 Temperature Monitor

Method Description
connect() / disconnect() Manage serial connection
read_temperature(channel) Read temperature from channel
read_all_temperatures() Read all channels
set_channel_parameters(...) Configure channel
get_system_parameters() Read system config
from pylabdrivers.omega_dp9800 import DP9800

dp9800 = DP9800(port="COM10", baudrate=38400, timeout=2.0)
dp9800.connect()
for ch in range(1, 9):
    print(f"Channel {ch}: {dp9800.read_temperature(ch):.2f} C")
dp9800.disconnect()

BK Precision 9200 Power Supply

Method Description
connect() / disconnect() Manage VISA connection
set_voltage(voltage) Set output voltage
set_current(current) Set output current
measure_voltage() Measure actual voltage
measure_current() Measure actual current
output_on() / output_off() Enable/disable output
from pylabdrivers.bk9200powersupply import BK9200PowerSupply

psu = BK9200PowerSupply(device_spec="ASRL6::INSTR")
psu.connect()
psu.set_voltage(12.0)
psu.set_current(1.5)
psu.output_on()
print(f"Voltage: {psu.measure_voltage()} V")
print(f"Current: {psu.measure_current()} A")
psu.output_off()
psu.disconnect()

Heidolph Overhead Stirrer

Method Description
connect() / disconnect() Manage serial connection
set_speed(rpm) Set stirring speed
get_actual_speed() Read actual speed
get_torque() Read torque (Ncm)
stop_stirrer() Stop stirring
get_device_info() Read device info
from pylabdrivers.heidolph import HeidolphStirrer, HeidolphStirrerError
import time

stirrer = HeidolphStirrer(port="COM3")
stirrer.connect()
stirrer.set_speed(100)
time.sleep(5)
print(f"Speed: {stirrer.get_actual_speed()} rpm")
stirrer.stop_stirrer()
stirrer.disconnect()

Keller LEX1 Pressure Sensor

Method Description
connect() / disconnect() Manage serial connection
get_serial() Read sensor serial number
set_address(new_address) Change device address
measure_p1() Measure pressure P1
measure_tob1() Measure temperature
from pylabdrivers.keller_lex1 import KellerLex1

sensor = KellerLex1(port="COM8", baud_rate=9600, address=1, timeout=0.3, echo=True)
print("Serial:", sensor.get_serial())
print(f"Pressure: {sensor.measure_p1():.3f} kPa")
sensor.disconnect()

Teledyne Foxy R1/R2 Fraction Collector

Controls Teledyne Foxy R1 and R2 fraction collectors via RS-232. Supports rack configuration, tube positioning, fraction program setup, valve control, and detector readout.

Method Description
connect() / disconnect() Manage serial connection
remote_mode() / local_mode() Toggle control mode
identify() Query instrument ID string
status() Read status code (0 = OK)
home() Home all axes
set_rack(rack) Select rack (0, 1, or 2)
get_current_rack() Read current rack
set_rack_type(rack, rack_type) Configure tube/vial type
set_last_tube(rack, tube_number) Set last tube limit
goto_tube(tube_number) Move to specific tube
get_current_tube() Read current tube position
next_tube() Advance to next tube
goto_tube_on_rack(rack, tube_number) Move to tube on specific rack
collect_mode() / drain_mode() Direct flow to tubes or waste
set_valve_collect() / set_valve_waste() Set valve state
get_valve_state() Read valve state
run() Start fraction collection
stop(full_stop) Pause or fully stop run
set_fraction_type(ftype) Set type (time/drops/external)
set_fraction_size(size) Set fraction size
set_delay(delay) Set pre-collection delay
read_detector() Read analog detector voltage
from pylabdrivers.foxyteledyne import FoxyFractionCollector, FoxyError
from config.config import load_config
import time

config = load_config()
foxy_conf = config["devices"]["foxy_teledyne"]

foxy = FoxyFractionCollector(
    port=foxy_conf["port"],
    baudrate=foxy_conf.get("baudrate", 9600),
    model=foxy_conf.get("model", "R2"),
)

try:
    foxy.connect()
    foxy.remote_mode()
    print("ID:", foxy.identify())

    foxy.set_rack_type(rack=1, rack_type=1)  # 12/13 mm tubes
    foxy.set_last_tube(rack=1, tube_number=80)
    foxy.home()
    foxy.set_rack(1)
    foxy.goto_tube(1)

    foxy.set_fraction_type(ftype=1)   # time-based
    foxy.set_fraction_size(size=30)   # 30 s per fraction
    foxy.collect_mode()
    foxy.run()

    time.sleep(10)
    print("Current tube:", foxy.get_current_tube())
    print("Detector:", foxy.read_detector(), "V")
    foxy.stop(full_stop=True)
except FoxyError as e:
    print("Error:", e)
finally:
    foxy.local_mode()
    foxy.disconnect()

SiELC Alltesta Mini Autosampler

Python driver for the SiELC Alltesta autosampler communicating over FTDI USB-serial at 115200 baud. Provides full control of needle positioning, tray/arm movement, valve switching, syringe plunger operations, shaking, and system calibration.

Method Description
connect() / disconnect() Manage FTDI serial connection
get_firmware_version() Read firmware version
get_protocol_version() Read protocol version
get_serial_number() Read device serial number
calibrate_all() Full system calibration
calibrate_needle() / calibrate_arm() / calibrate_tray() / calibrate_valve() / calibrate_plunger() Individual calibrations
set_tray_type(tray_type) Set tray (48, 96, or 384 vials)
move_tray_arm_to_vial(vial) Position over a vial
move_tray_arm_to_injection_port() Move to injection port
move_needle(height_mm) Move needle to height
move_needle_to_upper_position() Raise to safe travel height
move_needle_to_bottom_of_vial(vial) Lower needle into vial
move_needle_to_injection_port() Insert into injection port
move_valve_to_position(position) Switch multi-position valve
get_valve_position() Read valve position
move_plunger_to_position(uL) Move plunger to volume
zero_plunger() Return plunger to zero
set_draw_flow_rate(uL_per_min) Set aspiration rate
set_refill_flow_rate(uL_per_min) Set dispense rate
shake_tray(seconds) Shake tray for mixing
set_fan_speed(percent) Set cooling fan speed
get_command_state() Read device state
from pylabdrivers.sielc_alltesta_autosampler import (
    Autosampler, AutosamplerError, TrayType, ValvePosition
)
from config.config import load_config

config = load_config()
alltesta_conf = config["devices"]["sielc_alltesta"]

sampler = Autosampler(port=alltesta_conf["port"], auto_connect=False)

try:
    sampler.connect()
    print("Firmware:", sampler.get_firmware_version())

    sampler.calibrate_all()
    sampler.set_tray_type(TrayType.VIALS_96)

    # Move to vial 1, draw 100 uL
    sampler.move_tray_arm_to_vial(vial=1)
    sampler.move_needle_to_bottom_of_vial()
    sampler.set_draw_flow_rate(500)
    sampler.move_plunger_to_position(100)

    # Raise needle and go to injection port
    sampler.move_needle_to_upper_position()
    sampler.move_tray_arm_to_injection_port()

    # Dispense
    sampler.move_needle_to_injection_port()
    sampler.set_refill_flow_rate(1000)
    sampler.zero_plunger()
except AutosamplerError as e:
    print("Error:", e)
finally:
    sampler.disconnect()

Huber Pilot ONE Laboratory Chiller

Controls Huber laboratory chillers equipped with the Pilot ONE controller via the pySoftcheck library. Uses the process-bus (PB) protocol over RS-232.

Prerequisite: Download pySoftcheck from Huber and install it before using this driver:

pip install pysoftcheck-3.0.0-py3-none-any.whl

pySoftcheck is Huber software under the Huber Software License and is not redistributed in this repository.

Method Description
connect() / disconnect() Manage serial connection
set_temperature(celsius) Set temperature setpoint
get_setpoint() Read current setpoint
get_temperature() Read actual bath temperature
from pylabdrivers.huber_pilotone import HuberPilotOne, HuberPilotOneError
import time

chiller = HuberPilotOne(port="COM16")
chiller.connect()
chiller.set_temperature(25.0)

for i in range(5):
    time.sleep(2)
    actual = chiller.get_temperature()
    print(f"Actual: {actual} C")

chiller.disconnect()

Examples

Standalone example scripts are in the examples/ folder:

python examples/example_alicatmfc.py
python examples/example_foxyteledyne.py
python examples/example_sielc_alltesta.py
python examples/example_huber_pilotone.py

Running Tests

Unit tests use pytest with mocked hardware:

# Run all tests
pytest

# Run tests for a specific driver
pytest tests/test_foxyteledyne.py -v
pytest tests/test_sielc_alltesta.py -v

FAQs / Known Issues

How do I connect to my device? Update config/config.yaml with your port and settings, then use load_config() in your scripts.

Serial communication errors? Verify the correct port, baud rate, and cable connections. Adjust timeouts if needed.

Known limitations:

  • Some devices require tuning of inter-command delays
  • NI-DAQmx drivers require the NI runtime installed
  • Serial port access may require elevated privileges on some systems
  • Modbus TCP (Watlow F4T) requires network access to the controller

Contributing

Contributions are welcome. Please fork the repository, create a feature branch, add tests for new drivers, and submit a pull request.


License

This project is licensed under the MIT License. See LICENSE.txt for details.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages