Metadata-Version: 2.4
Name: ws6-200-wvm
Version: 0.1.0
Summary: Python client for the HighFinesse WS/6-200 wavemeter (wlmData wrapper).
Author-email: Marcello Laurel <mlaurel@mit.edu>
Keywords: highfinesse,laser,wavemeter,wlmdata,ws6
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.13
Requires-Dist: pydantic>=2
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: twine>=5; extra == 'dev'
Description-Content-Type: text/markdown

# ws6-200-wvm

Python client for the HighFinesse WS/6-200 wavemeter (and other WS/6
variants that share the same `wlmData` library).

The HighFinesse stack is a shared-memory client/server: the vendor's
`wlm_ws6` application owns the USB device and publishes measurements
into shared memory; this package loads `wlmData.dll` (Windows) or
`libwlmData.so` (Linux) via `ctypes` and reads from that shared memory.
Reads are microsecond-scale memory operations.

**Constraints:**

- The vendor library is **x86_64 only** — this package will not work
  on a Raspberry Pi / ARM host. Run on a Windows or Linux x86_64 PC
  attached to the wavemeter over USB.
- The vendor server (`wlm_ws6.exe`) must be running locally. On Windows,
  this package can autostart it for you (default).

## Install

```
pip install ws6-200-wvm
```

or with [uv](https://docs.astral.sh/uv/):

```
uv add ws6-200-wvm
```

## Prerequisites

1. **HighFinesse "Wavelength Meter" application** installed from the
   USB stick that shipped with your WS6. The installer is
   calibration-keyed to your unit's serial, so use the one for *your*
   device. It places `wlmData.dll` under `C:\Windows\System32\` and
   `wlm_ws6.exe` somewhere on PATH.

   Linux equivalent: request the Linux bundle from HighFinesse
   support; it installs `libwlmData.so` and a `wlm_ws6` daemon binary.

2. **USB connection** from the host to the WS6, and the wavemeter
   powered on. First time, run the vendor "Wavelength Meter" GUI to
   confirm it sees the device.

If you are integrating this package into a larger application and need
to write an adapter, see [docs/wrapping.md](docs/wrapping.md).

## Quickstart

```python
from ws6200_wvm import Ws6200

with Ws6200(channel=1) as wm:
    print(wm.idn)
    print(wm.measure_frequency_thz(), "THz")
    print(wm.measure_wavelength_nm(), "nm")
```

Or without the context manager:

```python
wm = Ws6200(channel=1)
wm.connect()
try:
    freq_thz = wm.measure_frequency_thz()
finally:
    wm.disconnect()
```

### Configuration

`Ws6200Config` is a pydantic model that you can populate from YAML /
JSON / env vars and call `.make()` on to get a driver:

```python
from ws6200_wvm import Ws6200Config

cfg = Ws6200Config(channel=1, autostart_server=True, start_timeout_s=10.0)
wm = cfg.make()
```

Fields:

| field              | default | meaning                                                                |
| ------------------ | ------- | ---------------------------------------------------------------------- |
| `channel`          | `1`     | 1-indexed switcher channel.                                            |
| `dll_path`         | `None`  | Override path to `wlmData.dll` / `libwlmData.so`. Auto-detected if unset. |
| `autostart_server` | `True`  | On Windows, launch `wlm_ws6.exe` via `ControlWLMEx` if not running.    |
| `start_timeout_s`  | `10.0`  | Seconds to wait for `wlm_ws6` to come up.                              |

## CLI

A console script `ws6200-wvm` is installed for first-install smoke
tests:

```
ws6200-wvm measure                       # one reading, then exit
ws6200-wvm measure --watch --interval 0.5
ws6200-wvm measure --channel 2 --dll-path "C:\path\to\wlmData.dll"
```

## Errors

Library / setup problems raise `RuntimeError`:

- "could not load HighFinesse wlmData library …" — install the vendor
  software, or pass `dll_path=...` to point at it.
- "HighFinesse wlm server is not running …" — start `wlm_ws6.exe`
  manually, or pass `autostart_server=True` on Windows.

Wavemeter-reported measurement errors raise `WavemeterError` with a
label from `wlmData.h`:

| label              | meaning                              |
| ------------------ | ------------------------------------ |
| `noval`            | No measurement available yet         |
| `nosig`            | No input signal                      |
| `badsig`           | Signal present but uncomputable      |
| `under`            | Underexposed                         |
| `over`             | Overexposed                          |
| `nowlm`            | No wavemeter server running          |
| `outofrange`       | Outside calibrated range             |
| `noresponse`       |                                      |
| `div0`             |                                      |
| `outofresolution`  |                                      |

`NotConnectedError` (subclass of `WavemeterError`) is raised if you
call a `measure_*` method before `connect()`.

## Calibration

HighFinesse units use an internal reference but the factory absolute
calibration drifts over time. Check the recommendation that came with
your unit (typically 12–24 months) and send it back in cycle to keep
the 200 MHz absolute spec.

## Releasing

This section is for maintainers publishing to PyPI.

### One-time setup

Create a PyPI account and a per-project API token at
<https://pypi.org/manage/account/token/>, then add it to `~/.pypirc`:

```ini
[pypi]
username = __token__
password = pypi-<your-token-here>

[testpypi]
repository = https://test.pypi.org/legacy/
username = __token__
password = pypi-<your-testpypi-token-here>
```

`twine` is already in the `dev` extra, so `uv sync --extra dev` is all
the local install you need.

### Publishing the first version (0.1.0)

```
rm -rf dist/
uv build
uv run twine check dist/*
uv run twine upload dist/*
```

A dry-run against TestPyPI is recommended before the first real
publish:

```
uv run twine upload --repository testpypi dist/*
pip install --index-url https://test.pypi.org/simple/ ws6-200-wvm
```

### Publishing a new version

PyPI versions are **immutable** — once `0.1.0` is uploaded you cannot
replace it (you can only "yank" it, which hides it from new installs
but keeps it resolvable for anyone already pinned). To publish a fix
or change, bump the version first.

1. **Bump both version strings in lockstep**:
   - `version = "X.Y.Z"` in [pyproject.toml](pyproject.toml)
   - `__version__ = "X.Y.Z"` in [src/ws6200_wvm/__init__.py](src/ws6200_wvm/__init__.py)
2. **Commit and tag**:
   ```
   git commit -am "release X.Y.Z"
   git tag vX.Y.Z
   ```
3. **Rebuild from a clean `dist/`** (otherwise `twine upload dist/*`
   will try to re-upload the old version's files and fail):
   ```
   rm -rf dist/
   uv build
   uv run twine check dist/*
   uv run twine upload dist/*
   ```
4. **Push the tag**: `git push && git push --tags`.

### Versioning policy

While pre-1.0:

- **Minor** bumps (`0.1.x → 0.2.0`) may break API. Downstream consumers
  should pin `>=0.1,<0.2` (see [docs/wrapping.md](docs/wrapping.md#adding-the-dependency)).
- **Patch** bumps (`0.1.0 → 0.1.1`) must remain backwards-compatible.

After 1.0, follow standard semver.

### Yanking a broken release

If a published version has a critical bug, yank it on PyPI:

```
uv run twine upload --skip-existing dist/*   # no-op, just for reference
```

The actual yank is a web-UI action: go to
<https://pypi.org/manage/project/ws6-200-wvm/releases/> → select the
version → **Options → Yank**. Then bump the patch version and
re-publish per the steps above. Anyone who already pinned the yanked
version keeps working; new resolves skip it.

