Python drivers for remote control of electronic chemistry lab instruments
Installation •
Supported Devices •
API Reference •
Tests •
Contributing
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.
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
#
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)
# 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
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
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 ()
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 ()
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 ()
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 ()
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 ()
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 ()
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
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
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
Contributions are welcome. Please fork the repository, create a feature branch, add tests for new drivers, and submit a pull request.
This project is licensed under the MIT License. See LICENSE.txt for details.