Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .claude/skills/pyhardwarelibrary/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,11 @@ the capabilities it declares — check the table.

| Capability | Methods |
|---|---|
| `OnOffControl` | `turnOn()`, `turnOff()`, `isLaserOn()`, `canTurnOn()` |
| `ShutterControl` | `openShutter()`, `closeShutter()`, `isShutterOpen()` |
| `PowerControl` | `setPower(watts)`, `power()` |
| `InterlockControl` | `interlock()` |
| `WavelengthControl` | `setWavelength(nm)`, `wavelength()`, `wavelengthRange()` |
| `OnOffCapability` | `turnOn()`, `turnOff()`, `isLaserOn()`, `canTurnOn()` |
| `ShutterCapability` | `openShutter()`, `closeShutter()`, `isShutterOpen()` |
| `PowerCapability` | `setPower(watts)`, `power()` |
| `InterlockCapability` | `interlock()` |
| `WavelengthCapability` | `setWavelength(nm)`, `wavelength()`, `wavelengthRange()` |

```python
from hardwarelibrary.sources.millennia import MillenniaEv25Device
Expand All @@ -153,7 +153,7 @@ laser.shutdownDevice()

- Cobolt: `CoboltDevice(portPath="COM3")` — OnOff + Power (+ autostart constraints; it
may refuse `turnOn()` when autostart is on, raising `CoboltCantTurnOnWithAutostartOn`).
- Matisse: `MatisseDevice(...)` over TCP — `WavelengthControl` (`setWavelength`/`wavelength`)
- Matisse: `MatisseDevice(...)` over TCP — `WavelengthCapability` (`setWavelength`/`wavelength`)
plus BiFi/etalon/piezo/scan methods.

### Power meters — Gentec-EO Integra
Expand Down Expand Up @@ -181,7 +181,7 @@ v = daq.getAnalogVoltage(channel=0) # read
daq.setAnalogVoltage(2.5, channel=1) # write (U3 DACs are slow PWM)
bit = daq.getDigitalValue(channel=4)
daq.setDigitalValue(1, channel=5)
# Hardware-timed acquisition: daq.acquireWaveform(...) (AnalogInputStreamDevice)
# Hardware-timed acquisition: daq.acquireWaveform(...) (AnalogInputStreamCapability)
daq.shutdownDevice()
```

Expand Down
37 changes: 35 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ API changes can land even when the minor version is unchanged.
lab Genesis CX-Vis (head `G532`). A HOPS supply is not a serial device: its
FTDI FT2232 (`0x0403:0x6010`) is driven as bit-banged I2C, with power DAC, ADC,
shutter/enable GPIO, and the head identity/calibration EEPROM all on one I2C
bus (see `manuals/Coherent-HOPS-*`). `VerdiGDevice` combines `OnOffControl`,
`ShutterControl`, `PowerControl`, and `InterlockControl`, and drives the bus
bus (see `manuals/Coherent-HOPS-*`). `VerdiGDevice` combines `OnOffCapability`,
`ShutterCapability`, `PowerCapability`, and `InterlockCapability`, and drives the bus
through an interchangeable `HOPSInterface`:
- `HOPSNativeInterface` (`sources/hopsnative.py`): **pure-Python** pyftdi I2C,
no DLL (macOS/Linux). Hardware-confirmed end to end on the lab unit
Expand All @@ -28,6 +28,39 @@ API changes can land even when the minor version is unchanged.
decode in `manuals/Coherent-HOPS-2-USB-and-DLL-Protocol.md` and
`manuals/Coherent-HOPS-3-I2C-Wire-Protocol.md`.

### Changed
- **Breaking:** capability mixins across all families now use a uniform
`*Capability` suffix, reserving `*Device` for instantiable hardware drivers.
Public methods and behavior are unchanged; only the mixin class names change.
Drivers subclassing these must update their base-class lists and imports.
- DAQ: `AnalogInputDevice` -> `AnalogInputCapability`, `AnalogOutputDevice` ->
`AnalogOutputCapability`, `AnalogIODevice` -> `AnalogIOCapability`,
`AnalogInputStreamDevice` -> `AnalogInputStreamCapability`,
`DigitalInputDevice` -> `DigitalInputCapability`, `DigitalOutputDevice` ->
`DigitalOutputCapability`, `DigitalIODevice` -> `DigitalIOCapability`,
`PhaseLockedDetectionDevice` -> `PhaseLockedDetectionCapability`,
`TriggerableDevice` -> `TriggerCapability`.
- Laser sources: `OnOffControl` -> `OnOffCapability`, `ShutterControl` ->
`ShutterCapability`, `PowerControl` -> `PowerCapability`, `InterlockControl`
-> `InterlockCapability`, `AutostartControl` -> `AutostartCapability`,
`WavelengthControl` -> `WavelengthCapability`, `DispersionControl` ->
`DispersionCapability`.
- Power meters: `WavelengthCalibratable` -> `WavelengthCalibrationCapability`,
`AutoScalable` -> `AutoScaleCapability`, `ScaleAdjustable` ->
`ScaleCapability`.
- **Breaking:** all capability mixins are consolidated into a single module,
`hardwarelibrary/capabilities.py`, and share one `Capability` base class (the
per-family `sources/capabilities.py`, `powermeters/capabilities.py`, and
`daq/daqdevice.py` are removed; the DAQ enums `InputSource`, `TriggerSource`,
`SampleClock` move there too, and the acquisition notification enum is now
nested as `AnalogInputStreamCapability.Notification`). Imports must point at
`hardwarelibrary.capabilities` (the family package `__init__`s still re-export
their own mixins, so `from hardwarelibrary.daq import AnalogIOCapability` and
the like keep working). `capabilities()` / `hasCapability()` are hoisted onto
`PhysicalDevice`, so every device -- including DAQ drivers -- now supports
capability introspection; the duplicated methods on `LaserSourceDevice` and
`PowerMeterDevice` are gone (`LaserSourceDevice` is now a pure marker).

## [1.4.0] - 2026-07-08

### Added
Expand Down
9 changes: 5 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,17 @@ Each family has an **abstract base class**. A driver subclasses it and implement

**Thorlabs linear motion** is a backend dispatcher: `ThorlabsDevice` (in `motion/thorlabs.py`) routes to `ThorlabsKinesisDevice`, which drives the stage through `pylablib`'s Kinesis support. The old FTDI path (`ThorlabsFTDIDevice`) was dropped. Install with the `thorlabs` extra (`pip install -e .[thorlabs]`).

Both the **DAQ and laser-source families use interface-segregated capability mixins** instead of one fat base class, because a device may implement any subset of the capabilities.
All families **use interface-segregated capability mixins** instead of one fat base class, because a device may implement any subset of the capabilities. Every mixin lives in the single module `hardwarelibrary/capabilities.py` and subclasses the one `Capability` base there; mixins carry the `*Capability` suffix, and only instantiable hardware drivers are named `*Device`. `PhysicalDevice` provides `capabilities()` / `hasCapability(cls)`, which introspect any device by walking its MRO for `Capability` subclasses — so every family gets capability introspection for free.

- DAQ (`daq/daqdevice.py`): `AnalogInputDevice` (`getAnalogVoltage`), `AnalogOutputDevice` (`setAnalogVoltage`), `DigitalInputDevice` (`getDigitalValue`), `DigitalOutputDevice` (`setDigitalValue`), plus `AnalogIODevice` / `DigitalIODevice` that combine each pair, and `AnalogInputStreamDevice` for hardware-timed acquisition. The `configure*` and `direction*` methods are optional no-op hooks. Example: `class LabjackDevice(PhysicalDevice, AnalogIODevice, DigitalIODevice, AnalogInputStreamDevice)`.
- Laser sources (`sources/capabilities.py`): `OnOffControl` (`turnOn`/`turnOff`/`isLaserOn`), `ShutterControl` (`openShutter`/`closeShutter`/`isShutterOpen`), `PowerControl` (`setPower`/`power`), `InterlockControl` (`interlock`), `AutostartControl`, `WavelengthControl` (`setWavelength`/`wavelength`), `DispersionControl`. Each exposes a public method that calls a `do*` abstract hook the driver implements. `LaserSourceDevice` is a thin marker base; the behavior comes from the mixins. Examples: `class CoboltDevice(LaserSourceDevice, OnOffControl, PowerControl, ...)`, `class MillenniaEv25Device(LaserSourceDevice, OnOffControl, ShutterControl, PowerControl)`, `class MatisseDevice(PhysicalDevice, WavelengthControl)`.
- DAQ: `AnalogInputCapability` (`getAnalogVoltage`), `AnalogOutputCapability` (`setAnalogVoltage`), `DigitalInputCapability` (`getDigitalValue`), `DigitalOutputCapability` (`setDigitalValue`), plus `AnalogIOCapability` / `DigitalIOCapability` that combine each pair, and `AnalogInputStreamCapability` for hardware-timed acquisition. The `configure*` and `direction*` methods are optional no-op hooks. Example: `class LabjackDevice(PhysicalDevice, AnalogIOCapability, DigitalIOCapability, AnalogInputStreamCapability)`.
- Laser sources: `OnOffCapability` (`turnOn`/`turnOff`/`isLaserOn`), `ShutterCapability` (`openShutter`/`closeShutter`/`isShutterOpen`), `PowerCapability` (`setPower`/`power`), `InterlockCapability` (`interlock`), `AutostartCapability`, `WavelengthCapability` (`setWavelength`/`wavelength`), `DispersionCapability`. Each exposes a public method that calls a `do*` abstract hook the driver implements. `LaserSourceDevice` is a thin marker base; the behavior comes from the mixins. Examples: `class CoboltDevice(LaserSourceDevice, OnOffCapability, PowerCapability, ...)`, `class MillenniaEv25Device(LaserSourceDevice, OnOffCapability, ShutterCapability, PowerCapability)`, `class MatisseDevice(PhysicalDevice, WavelengthCapability)`.
- Power meters: `WavelengthCalibrationCapability`, `AutoScaleCapability`, `ScaleCapability` (on top of the base `doGetAbsolutePower` every meter implements).

## Adding a new device

Reference implementation: `hardwarelibrary/daq/labjackdevice.py`. The pattern:

1. Subclass the family base (it already extends `PhysicalDevice`). For DAQ, combine `PhysicalDevice` with the capability mixins you need, e.g. `class FooDAQ(PhysicalDevice, AnalogIODevice)`.
1. Subclass the family base (it already extends `PhysicalDevice`). For DAQ, combine `PhysicalDevice` with the capability mixins you need, e.g. `class FooDAQ(PhysicalDevice, AnalogIOCapability)`.
2. Set class attributes `classIdVendor` and `classIdProduct` (USB VID/PID, or the equivalent for serial-only devices).
3. Implement `doInitializeDevice` and `doShutdownDevice`. Keep them minimal — open the port, close the port.
4. Implement the family's abstract hooks (see the table). Omitting one raises `TypeError` at instantiation, so the class will not even construct until the contract is complete.
Expand Down
9 changes: 9 additions & 0 deletions docs/source/api/core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ PhysicalDevice

.. automodule:: hardwarelibrary.physicaldevice

Capabilities
------------

Interface-segregated capability mixins shared by every device family. Each
subclasses the single ``Capability`` base; ``PhysicalDevice`` introspects them
through ``capabilities()`` / ``hasCapability()``.

.. automodule:: hardwarelibrary.capabilities

DeviceManager
-------------

Expand Down
9 changes: 3 additions & 6 deletions docs/source/api/daq.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
Data acquisition (DAQ)
======================

Capability mixins and DAQ drivers.

DAQDevice
---------

.. automodule:: hardwarelibrary.daq.daqdevice
DAQ drivers. The DAQ capability mixins (``AnalogIOCapability``,
``DigitalIOCapability``, ``AnalogInputStreamCapability``, ...) live in
:mod:`hardwarelibrary.capabilities`; see the Core page.

LabJack
-------
Expand Down
6 changes: 2 additions & 4 deletions docs/source/api/sources.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ LaserSourceDevice

.. automodule:: hardwarelibrary.sources.lasersourcedevice

Capabilities
------------

.. automodule:: hardwarelibrary.sources.capabilities
The laser-source capability mixins (``OnOffCapability``, ``ShutterCapability``,
...) live in :mod:`hardwarelibrary.capabilities`; see the Core page.

Cobolt
------
Expand Down
Loading
Loading