Metadata-Version: 2.4
Name: labdaemon
Version: 1.0.1
Summary: A Python framework for instrument control and experiment automation.
Project-URL: Homepage, https://docs.samsci.com/labdaemon/
Project-URL: Documentation, https://docs.samsci.com/labdaemon/
Project-URL: Repository, https://github.com/qnslab/labdaemon
Project-URL: Issues, https://github.com/qnslab/labdaemon/issues
Author: The LabDaemon Team
License: AGPL-3.0-or-later
License-File: LICENSE
Keywords: automation,control,experiment,instrument,laboratory
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.8
Requires-Dist: loguru
Requires-Dist: numpy
Provides-Extra: dev
Requires-Dist: fastapi; extra == 'dev'
Requires-Dist: httpx; extra == 'dev'
Requires-Dist: numpy; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: requests>=2.25.0; extra == 'dev'
Requires-Dist: sse-starlette; extra == 'dev'
Requires-Dist: uvicorn; extra == 'dev'
Provides-Extra: launcher
Requires-Dist: psutil; extra == 'launcher'
Requires-Dist: pyqt5>=5.15.0; extra == 'launcher'
Requires-Dist: requests>=2.25.0; extra == 'launcher'
Provides-Extra: server
Requires-Dist: fastapi; extra == 'server'
Requires-Dist: numpy; extra == 'server'
Requires-Dist: requests>=2.25.0; extra == 'server'
Requires-Dist: sse-starlette; extra == 'server'
Requires-Dist: uvicorn; extra == 'server'
Description-Content-Type: text/markdown

<p align="center">
  <img src="mascot-200.png" alt="LabDaemon Mascot" width="150">
</p>

# LabDaemon

Write device drivers and tasks once, run everywhere. Start with simple scripts and painlessly move to GUI and then server. Plain Python device classes, safe concurrency, reusable across scripts, GUIs, and automated experiments. Optional HTTP/SSE server for remote access and multi-client coordination.

## Why LabDaemon?

- **Write once, run anywhere:** Same device code works in scripts, GUIs, and web interfaces
- **Thread-safe by default:** Automatic per-device locking lets you write simple single-threaded code
- **Progressive complexity:** Start local, add server when you need remote access
- **Plain Python classes:** No framework lock-in or inheritance hierarchies
- **Real-world tested:** Designed for research labs with GPIB, DAQs, lasers, and detectors

## Documentation

[LabDaemon docs](https://docs.samsci.com/labdaemon/)

[LabDaemon template](https://github.com/qnslab/labdaemon-template) - Structuring lab code around this framework

## Quick Start

### Local Script

```python
import labdaemon as ld

class MyLaser:
    def __init__(self, device_id: str, address: str, **kwargs):
        self.device_id = device_id
        self.address = address
        self._instrument = None
    
    def connect(self):
        import pyvisa
        rm = pyvisa.ResourceManager()
        self._instrument = rm.open_resource(self.address)
    
    def disconnect(self):
        if self._instrument:
            self._instrument.close()
    
    def set_wavelength(self, wl: float):
        self._instrument.write(f'WAVELENGTH {wl}')

# Use it
daemon = ld.LabDaemon()
daemon.register_plugins(devices={"MyLaser": MyLaser})

with daemon.device_context("laser1", "MyLaser", address="GPIB0::1") as laser:
    laser.set_wavelength(1550.0)

daemon.shutdown()
```

### Add Server for Remote Access

```python
from labdaemon.server import LabDaemonServer

daemon = ld.LabDaemon()
server = LabDaemonServer(daemon, host="0.0.0.0", port=5000)

# Same device code, now accessible via HTTP
daemon.register_plugins(devices={"MyLaser": MyLaser})
laser = daemon.add_device("laser1", "MyLaser", address="GPIB0::1")
daemon.connect_device(laser)

try:
    server.start(blocking=False)
    print("Server running at http://localhost:5000")
    
    # Control locally OR via HTTP/SSE
    laser.set_wavelength(1550.0)
    
    input("Press Enter to stop...")
finally:
    server.stop()
    daemon.shutdown()
```

## Installation

```bash
pip install labdaemon[server]
```