Metadata-Version: 2.5
Name: plesty-pi-mirror-controller
Version: 0.1.0
Summary: A plesty pi mirror controller device.
Author-email: Maximilian Heller <maximilian.heller@fkp.uni-hannover.de>, Yunshuang Yuan <yunshuang.yuan@fkp.uni-hannover.de>
Maintainer-email: Plesty Development Team <plesty.dev@example.com>
License-Expression: LGPL-3.0-or-later
License-File: COPYING
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: plesty-lib>=0.5.0
Requires-Dist: pyserial
Requires-Dist: pyyaml>=6.0
Provides-Extra: gui
Requires-Dist: plesty-lib[gui]>=0.5.0; extra == 'gui'
Description-Content-Type: text/markdown

# Plesty Pi Mirror Controller Device

A [Plesty](https://plesty.dev) device driver for a Raspberry Pi Pico W-based mirror controller. It controls servo motors and LED driver channels (MCP4728 DAC) over a binary serial protocol via USB.

**Hardware setup:**
- Raspberry Pi Pico W connected via USB (default `/dev/ttyACM0`)
- Servo motors on GPIO pins 0–12
- LED/laser drivers on MCP4728 DAC channels `a`–`d`


## Installation

```bash
git clone https://gitlab.com/plesty/hub/devices/others/plesty-pi-mirror-controller.git
cd plesty-pi-mirror-controller
uv sync
uv pip install -e .
```

On Linux, add your user to the `dialout` group for serial port access:

```bash
sudo usermod -aG dialout $USER  # log out and back in to apply
```


## Usage

```python
from plesty.pi_mirror_controller.device import Device

with Device(port="/dev/ttyACM0", servos={"mirror": 4}, leds={"laser": "a"}) as dev:
    print(dev.identity())

    # Direct control
    dev.set_servo("mirror", 1)       # extend
    dev.set_voltage("laser", 2.5)    # 2.5 V

    # Parameter get/set via ConfigSystem
    dev.write("mirror", 0)           # retract via config interface
    dev.write("laser", 1.0)          # 1.0 V via config interface
    print(dev.query("laser"))        # returns last set value

    # Composite operations
    dev.set_all_servos(0)
    dev.set_all_voltages(0.0)
    dev.reset_all()

    # Runtime reconfiguration
    dev.configure(servos={"mirror": 4, "shutter": 7})
```

See [`examples/example.py`](examples/example.py) for a complete walkthrough.


## Run as TCP Server

Expose the device over the network as a Plesty TCP server. The serial port is
machine-specific and comes from `.env` (see `.env.example`); the servo/LED
mapping and server tuning live in `config/controller.yaml`. CLI flags
override both. Without an `leds` mapping, the four DAC channels are registered
under their own letters (`a`–`d`) and no servo is; voltages outside 0–5 V are
clamped to the range.

```bash
cp .env.example .env   # set the serial port
uv run python -m plesty.pi_mirror_controller --config config/controller.yaml
```

CLI options:

| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--port` | `-p` | `MIRROR_PORT` or `/dev/ttyACM0` | Serial port of the Pico W |
| `--config` | `-c` | `config/controller.yaml` | YAML with device mapping and tuning |
| `--tcp-port` | `-tp` | YAML value or `5555` | TCP port for the Plesty server |
| `--fixed-threading` | `-ft` | YAML value or `False` | Use fixed thread pool |
| `--gui` | | off | Open the control panel instead of serving (needs `uv sync --extra gui`) |
| `--skip-preflight` | | off | Serve even when the serial port is not present |

Before binding, the server checks that the OS lists the serial port (matched by
name, or by the Pico's USB serial number), without opening it. If the port is
missing, it stops and prints the ports it did find.

The port `mock` opens an in-memory simulator instead of a serial port: every call
works and the bytes are recorded, with no controller attached.


## Protocol

The firmware expects 2-byte binary messages over serial at 115200 baud:

| Phase | Bytes | Meaning |
|-------|-------|---------|
| Config | `c\x00` | Start configuration |
| Config | `s<pin>` | Register servo on GPIO pin |
| Config | `a<ch>` | Register LED on DAC channel (`a`–`d`) |
| Config | `e\x00` | End configuration |
| Operation | `<index><value>` | Set device at index to value (0–255) |

Servo values: `0` = retracted, `1` = extended. LED voltages: 0–5 V mapped linearly to 0–255.
