Metadata-Version: 2.4
Name: daisylab
Version: 0.3.12
Summary: Python library for Daisy laboratory instruments over USB serial
Author: Scalables, LLC
Maintainer: Lui Nobrega
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/ScalablesLab/daisy-python-library
Project-URL: Repository, https://github.com/ScalablesLab/daisy-python-library
Project-URL: Issues, https://github.com/ScalablesLab/daisy-python-library/issues
Keywords: daisy,laboratory,automation,serial,pump,valve,instrument,pyserial
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: System :: Hardware :: Hardware Drivers
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyserial>=3.5
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Provides-Extra: publish
Requires-Dist: build; extra == "publish"
Requires-Dist: twine; extra == "publish"
Dynamic: license-file

# daisylab

Python library for controlling **Daisy** laboratory instruments over USB serial.

`daisylab` discovers Daisy hardware on your serial ports, validates the
connected instruments against what your code expects, and gives you a clean,
per-instrument API for pumps, valves, and motion stages — with background
threads handling the serial protocol for you.

## Installation

```bash
pip install daisylab
```

Requires Python 3.9+ and [`pyserial`](https://pypi.org/project/pyserial/) 3.5+.

## Quick start

```python
import daisylab

with daisylab.connect() as lab:
    print("Found ports:", lab.ports)
    print("Instrument types:", lab.instruments_list)

    # Declare the instruments on a port, in physical order, by type code.
    group = daisylab.InstrumentGroup([10, 2])   # Peri T + Multi-Valve
    lab.add_group(group)

    pump  = group.inst[0]   # DaisyPeriT
    valve = group.inst[1]   # DaisyMultiValve

    valve.home()
    pump.run(speed=50.0, volume=10.0, wait=True)   # 50 ml/min, 10 ml
    valve.select_port(3, wait=True)
```

`connect()` returns a `DaisyInterface` (usable as a context manager). Each
`InstrumentGroup` is bound to a serial port in the order you call
`add_group()`.

## Supported instruments

Instruments are declared by integer **type code** (the value the firmware
reports during roll-call) or by a case-insensitive name substring such as
`"Peri T"` or `"gantry"`.

| Code | Class | Hardware |
|------|-------|----------|
| 1  | `DaisyPeriS`        | Peri pump S |
| 2  | `DaisyMultiValve`   | 12-port rotary valve |
| 3  | `DaisySolVal`       | Solenoid valve array (≤8) |
| 4  | `DaisyPistonPump`   | Piston pump (1 ml) |
| 5  | `DaisyPeriPumpXL`   | Peri pump XL |
| 6  | `DaisyMultiValveXL` | Rotary valve XL |
| 7  | `DaisySolValXL`     | Solenoid valve array XL |
| 8  | `DaisyGantry`       | XY Cartesian stage |
| 9  | `DaisyZStage`       | Z linear stage |
| 10 | `DaisyPeriT`        | Peri pump T |
| 11 | `DaisyPeriP`        | Peri pump P |
| 12 | `DaisyPistonPump`   | Piston pump (5 ml) |
| 13 | `DaisyProcess`      | Multi-channel pH/sensor |

### Peristaltic pump speed limits

Peri pumps enforce a per-model maximum **motor speed**. `run(speed, volume)`
converts the requested flow (ml/min) to RPM using the current calibration and
raises `ValueError` before sending anything if it would exceed the cap:

| Model | Max RPM |
|-------|---------|
| Peri S / Peri P            | 150 |
| Peri T / Peri Pump XL      | 300 |

The cap is calibration-aware: recalibrating the tubing shifts the ml/min
ceiling but the motor is always held to its rated RPM.

## Examples

Standalone, runnable scripts live in [`daisylab/examples/`](daisylab/examples):

- `daisy_controller.py` — universal interactive controller for any instrument
- `z_stage_controller.py` — interactive Z-stage controller
- `z_stage_peri_t_sequence.py` — automated dispense sequence (home → lower → pump → raise)

Each supports a `--demo` flag that runs against simulated hardware (no serial
I/O required):

```bash
python daisylab/examples/daisy_controller.py --demo
```

A **sample Jupyter notebook** is also included in the package at
`daisylab/jupyter/sample.ipynb`. After installing, you can find it at
`<site-packages>/daisylab/jupyter/sample.ipynb`.

## Exceptions

Every error the library raises derives from **`DaisyError`**, so a single
`except daisylab.DaisyError` is a complete catch-all for any instrument:

- `DaisyTimeoutError` — no `OK` / a `BAD` reply within the timeout
- `DaisyConnectionError` — no devices found / connection failure
- `DaisyInstrumentMismatchError` — software list ≠ hardware roll-call
- `DaisyValueError` — invalid argument or value (e.g. speed above a pump's
  `MAX_RPM`, unknown type code, malformed device response). Also subclasses
  the built-in `ValueError`, so existing `except ValueError` handlers keep
  working.

## Stopping safely

`stop_all()` halts every instrument across all groups (best-effort — one
instrument's failure won't block the others); ports stay open so you can
recover. `safe_session()` wraps a command sequence and stops everything if any
exception propagates:

```python
with lab.safe_session():
    pump.run(speed=50, volume=10, wait=True)
    gantry.run(100, 150, wait=True)   # if this raises, the pump is stopped
# the original exception is re-raised after the stop

lab.stop_all()          # or halt everything manually (alias: lab.emergency_stop())
group.stop_all()        # or just one group
```

## Development

```bash
git clone https://github.com/ScalablesLab/daisy-python-library
cd daisy-python-library
pip install -e ".[test]"
pytest
```

The test suite is hardware-free — `tests/fakes.py` provides a synchronous
`FakeSerialManager` that synthesises device replies, so the real drivers run
end to end without any instruments attached.

## License

Apache License 2.0 — see [LICENSE](LICENSE). Copyright Scalables, LLC.
