Metadata-Version: 2.4
Name: kirlab
Version: 0.1.0
Summary: Control KirLab HIL devices from Python: load built projects, run them, read and write signals.
Author: Kirlab
License: Proprietary
Project-URL: Homepage, https://kirlab.com
Keywords: kirlab,hil,hardware-in-the-loop,fpga,power-electronics,ftdi
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Manufacturing
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: C++
Classifier: Operating System :: Microsoft :: Windows
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: System :: Hardware :: Hardware Drivers
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# kirlab

Python control of KirLab HIL devices. It wraps the same C++ device stack that
KirLab Studio uses, so a project built in the Studio behaves identically when
driven from a script.

```python
import kirlab

print(kirlab.list_devices())          # ['KL0000123', ...]

with kirlab.open_device() as dev:
    dev.load_project(r"C:\KirLab\projects\Buck")   # boots the built project
    dev.read_list = ["Vout", "IL"]                 # subscribe to signals
    dev.start()

    dev.write("DutyCycle", 0.42)
    print(dev.read("Vout"), dev.read_all())

    dev.stop()
```

## Install

```
pip install kirlab
```

Windows wheels bundle both `kirlabpy.dll` and the FTDI `FTD3XX.dll`, so there
is nothing else to install. The device also needs the FTDI D3XX driver, which
the KirLab Studio installer provides.

## What it does

| Task | API |
| --- | --- |
| List connected devices | `kirlab.list_devices()` |
| Open one | `kirlab.open_device(serial=None)` |
| Load a built project | `dev.load_project(path)` / `dev.load_binary(bin, interface)` |
| Run / halt | `dev.start()`, `dev.stop()`, `dev.pause()`, `dev.resume()` |
| Inspect | `dev.state`, `dev.boot_progress`, `dev.execution_time`, `dev.signals` |
| Write signals | `dev.write(name, value)`, `dev.write_many({...})`, `dev.write_raw(...)` |
| Read signals | `dev.set_read_list([...])`, `dev.read(name)`, `dev.read_all()`, `dev.sample()` |

## Loading a project

`load_project` expects a project **already built** by KirLab Studio; it does
not compile anything. It reads the Studio build layout:

```
<project_dir>/build/KirLab.bin        # bitstream sent to the device
<project_dir>/build/interface.yml     # signal table (name, type, address)
```

If your artefacts live elsewhere, use `dev.load_binary(binary, interface)`.

Loading blocks until the FPGA has the whole image. Pass `wait=False` to
return immediately and watch `dev.boot_progress` yourself:

```python
dev.load_project(path, wait=False)
while dev.state is kirlab.DeviceState.BOOTING:
    print(f"{dev.boot_progress:.0%}")
    time.sleep(0.2)
```

## Reading signals

Reading is a subscription, not a request. The device streams back a fixed list
of signals; `read()` returns the newest value from that stream.

```python
dev.set_read_list(["Vout", "IL", "Vin"], sampling_index=8)
dev.start()

sample = dev.sample()      # one synchronised reading of the whole list
print(sample.time, sample["Vout"], sample.index)
```

* At most **14** signals (`kirlab.MAX_READ_LIST`) -- the hardware has 15 usable slots
  and one carries the timestamp.
* `sampling_index` divides the rate by `2 ** sampling_index`. The default of 8
  matches Studio's monitor view.
* `sample.index` is a monotonic counter: unchanged between two calls means the
  device has not produced a new sample yet.
* `start()` returns once the device is **actually streaming**, so values read
  straight afterwards are real. Arming the read-back list takes about a second,
  and until it completes every signal reads `0.0` -- indistinguishable from a
  genuine zero. Pass `start(wait=False)` to skip that if you want to poll
  yourself. Changing the read list while running waits the same way.

Signals not in the read list raise `SignalNotFound`. Writing does not require a
subscription.

## Building from source

From the repository root, the batch scripts do everything:

```
build-python.cmd                  build the native library and stage it
build-python.cmd Release wheel    ...and produce dist\*.whl
build-python.cmd Debug clean      wipe the build dir, debug build

test-python.cmd                   create python\.venv, install, run the tests
test-python.cmd C:\path\to\Project   ...then run the quickstart on hardware
```

Underneath, the native library is built by CMake -- either on its own (no Qt
needed):

```
python build_native.py            # configure + build + stage into the package
python build_native.py --wheel    # ...and produce dist/*.whl
```

or as part of the Studio build tree, where `KIRLAB_BUILD_PYTHON` is ON by
default:

```
cmake -DKIRLAB_BUILD_PYTHON=ON -S . -B cmake-build-release
cmake --build cmake-build-release --target kirlabpy
```

Either way the output lands in `python/src/kirlab/_native/`, which is what the
wheel packs. Set `KIRLABPY_LIBRARY` to load a library from somewhere else.

Publish the **wheel only**. `python -m build` on its own produces a broken
wheel: it builds the sdist first and then the wheel from that sdist, and the
sdist deliberately excludes the platform-specific DLLs. Use `--wheel` and
`--sdist` separately, which is what the scripts above do.

## Troubleshooting

**`NativeLibraryNotFound`** -- the shared library was not built or not staged.
Run `python build_native.py`, or point `KIRLABPY_LIBRARY` at an existing build.

**`DeviceBusy`** -- the device is plugged in but another process holds it.
KirLab Studio opens every device it discovers, so close it first.

**`DeviceNotFound`** -- nothing with that serial is enumerated. Check the cable
and that the FTDI D3XX driver is installed.

**Silent device, no samples** -- check `dev.state` is `RUNNING` and the signal
is in `dev.read_list`.

To see the C++ layer's own logging (it is quiet by default at error level):

```python
kirlab.set_log_level(0)   # 0 debug, 1 peek, 2 info, 3 warning, 4 error
```
