Metadata-Version: 2.5
Name: plesty-kst201
Version: 0.1.0
Summary: A plesty kst201 device.
Author-email: Yunshuang Yuan <yuanyunshuang@gmail.com>
Maintainer-email: Plesty Development Team <plesty.dev@example.com>
License-Expression: LGPL-3.0-or-later
License-File: LICENSE
License-File: LICENSES/LGPL-3.0-or-later.txt
Requires-Python: >=3.12
Requires-Dist: ftd2xx>=1.3.8; sys_platform in 'win32'
Requires-Dist: plesty-lib<1,>=0.5.0
Requires-Dist: pyyaml>=6.0
Description-Content-Type: text/markdown

# Plesty KST201 Device

PLESTY device module for a three-axis **XYZ stage** built from three Thorlabs
**KST201** K-Cube stepper motor controllers, each driving a **ZST225** 25 mm linear
actuator, spoken to over the APT serial protocol. One server drives the whole stage:
there is one device, one TCP port, and the axes are told apart by name (`x`, `y`, `z`).
All positions are in millimetres.

## Supported hardware

| Item | Detail |
|---|---|
| Controllers | Three Thorlabs KST201 K-Cube stepper controllers, one per axis (serial numbers start with `26`) |
| Actuators | One ZST225 linear actuator per controller, travel 0 to 25 mm each |
| Link | USB, FTDI id `0403:FAF0`, exposed as a COM port by the Thorlabs driver |
| Host OS | Windows only for real hardware (see below); the mock runs anywhere |

On macOS nothing claims the Thorlabs FTDI product id, so the controller cannot
be reached there. The start-up preflight refuses non-Windows hosts with that
explanation. `--mock` is exempt.

## Installation

```bash
git clone https://gitlab.com/plesty/hub/devices/thorlab/plesty-kst201.git
cd plesty-kst201
uv sync
```

## Quick start

```bash
# Real stage (Windows): server on TCP port 5555
DEVICE_SERIAL_X=... DEVICE_SERIAL_Y=... DEVICE_SERIAL_Z=... \
  uv run python -m plesty.kst201 --tcp-port 5555

# No hardware: serve three in-memory APT motor simulators
uv run python -m plesty.kst201 --tcp-port 5555 --mock
```

### Picking the controllers

Each axis is its own controller, chosen by its **serial number**. Per axis, the first
of these that is set wins:

1. `--serial-x` / `--serial-y` / `--serial-z`
2. `DEVICE_SERIAL_X` / `DEVICE_SERIAL_Y` / `DEVICE_SERIAL_Z`
3. `running.axes.<axis>.serial` in the packaged `config.yaml` (empty; never commit a
   real serial, this repository is public)

All three axes are required. The preflight refuses to start the server, naming the
axis that has no serial, shares one with another axis, or is not found, and lists every
serial it saw over COM ports and over D2XX so you can pick the right one. A controller
is opened through the COM port that carries its serial; if none does, through the FTDI
D2XX device with that serial.

Copy `.env.example` to `.env` for the same settings in a file.

## Schema reference

### Parameters (groups `x`, `y`, `z`)

Every axis has the same five parameters, keyed `<axis>.<name>`, e.g. `x.MaxVelocity`.

| Name | Unit | Range | Default | Access | Description |
|---|---|---|---|---|---|
| `MaxVelocity` | mm/s | 0.01 to 2.0 | 1.0 | read/write | Maximum velocity of the trapezoidal profile |
| `Acceleration` | mm/s^2 | 0.01 to 4.0 | 0.5 | read/write | Acceleration of the trapezoidal profile |
| `Position` | mm | 0 to 25 | | read only | Position counter, without the custom zero |
| `UserPosition` | mm | | | read only | Position in the user frame (custom zero subtracted) |
| `CustomZero` | mm | | 0.0 | read/write | Software zero offset of that axis |

### Functions

`axis` is `"x"`, `"y"` or `"z"`. A move touches only the axis it names. A target outside
the 0 to 25 mm travel raises `ValueError` before any traffic is sent.

| Name | Description |
|---|---|
| `move_x(target)`, `move_y(target)`, `move_z(target)` | Move that axis to a user-frame position in mm |
| `move_axis(axis, position)` | The same, with the axis as an argument |
| `move_axis_relative(axis, step)` | Move that axis by a signed step in mm |
| `get_position(axis)` | User-frame position of that axis in mm |
| `get_positions()` | `{"x": ..., "y": ..., "z": ...}` in mm |
| `home_axis(axis)` | Run that controller's homing cycle, then move to 0 mm |
| `wait_settled(axis, timeout=None)` | Block until that axis has stopped, return its position |
| `stop_motion()` | Stop all three axes, return their positions |

The generated reference pages in `docs/` list the full schema.

## Communication

APT binary protocol over USB (FTDI `0403:FAF0`), connect timeout 5 s, move
timeout 60 s (`config.yaml`).

On Windows with Thorlabs Kinesis installed the controller is bound to FTDI's
D2XX driver and has no COM port. The module then opens it through D2XX (the
`ftd2xx` package, installed with the module on Windows): no "Load VCP" setting
is needed. **Close Kinesis first**, since it holds the controller while it runs.
A controller that does have a COM port is still reached through it.

## Status and open points

- Count scaling comes from pylablib's encoding of the Thorlabs table, not from
  the primary APT protocol PDF; confirm against the PDF.
- Homing, limit-switch and button parameters stored on the controller are not
  read or written; the controller keeps its own Kinesis settings.
- Velocity and acceleration limits are conservative, below the actuator's rating.
