Metadata-Version: 2.5
Name: dt5215
Version: 1.0.6
Summary: Control SDK of the Nuclear Instruments / CAEN DT5215 FERS concentrator: HTTP/JSON over Ethernet
Project-URL: Homepage, https://www.nuclearinstruments.eu
Project-URL: Documentation, https://downloads.nuclearinstruments.eu/packages/dt5215-control-sdk
Project-URL: Source, https://gitlab.nuclearinstruments.eu/public-repo/fers/dt5215-control-sdk
Author: Nuclear Instruments
License-Expression: MIT
License-File: LICENSE
Keywords: caen,concentrator,daq,dt5215,fers,nuclear instruments,scintillator,sipm,tdlink
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: Topic :: System :: Monitoring
Requires-Python: >=3.8
Provides-Extra: test
Requires-Dist: dt5215-emulator; extra == 'test'
Requires-Dist: pytest>=7; extra == 'test'
Description-Content-Type: text/markdown

# DT5215 Control SDK — Python

The control and monitoring API of the **DT5215** FERS concentrator, in Python, with no
dependencies: the standard library is enough.

```bash
pip install dt5215
```

```python
import dt5215

with dt5215.connect("192.168.50.125", username="user", password="user") as dev:
    info = dev.info()
    print(info.hostname, info.info.sw_release)

    for chain in dev.chains.status().enabled:
        print("chain", chain.chain, chain.board_count, "boards",
              "%.1f Mbps" % chain.mbps)

    for board in dev.chains.links().boards:
        print(board.id, board.name, board.tempBoard, "C", board.hv_Vmon, "V")
```

Python 3.8 and later, on Linux, Windows and macOS.

## What is in it

Everything the instrument's web interface can do, grouped the way the API is grouped:

| | |
|---|---|
| `dev.info()` `dev.alive()` `dev.error_flags()` `dev.leds()` `dev.reboot()` | identity, health, front panel, life cycle |
| `dev.chains` | the 8 TDlink chains: status, enumeration, the boards on them, heatmaps |
| `dev.boards` | the FERS boards: registers, HV, firmware upgrade over TDlink |
| `dev.io` | the front panel: LEMO direction and function, sync matrix, test pulse |
| `dev.clock` | the master PLL, the DDMTD phase detectors, the link phase |
| `dev.network` | the LAN, 10 GbE and USB interfaces, and their traffic |
| `dev.time` | the GPS receiver, the system clock, systemd-timesyncd |
| `dev.system` | the log, the debug counters, the configuration file |
| `dev.raw` | any endpoint, including one a newer firmware adds before this SDK names it |

Event data is **not** here: it leaves the instrument over the binary streaming sockets that
Janus and FERSlib speak. This SDK is the control plane.

## Three things worth knowing

**The firmware answers HTTP 200 with `result: false`.** A client that only reads the status
code believes every refusal succeeded. This SDK checks the body and raises `DeviceError`
with the instrument's own message — including the configuration validator's list of
complaints, which is otherwise hidden behind "configuration validation failed".

**Tokens expire** (7 days), and restarting the bridge application throws them all away. Pass
`username`/`password` and the SDK logs in when it needs to, notices a 401, logs in again and
retries the request once. `dev.alive()` is the one operation that answers without a token:
call it to tell "the instrument is down" from "my token is stale".

**Some operations take minutes.** `chains.rescan()` runs the whole enumeration and blocks
until it is over. To follow it instead:

```python
dev.chains.rescan_async()
status = dev.chains.wait_enumeration(on_progress=lambda s: print(s.progress, "%"))
print(dev.chains.enumeration_log())
```

The same shape works for the board firmware upgrade:

```python
info = dev.boards.fw_upload("firmware.ffu")      # what the file holds, and for which models
dev.boards.fw_start(dev.chains.links().boards)   # Board, BoardRef, (chain, node) or "0:1"
status = dev.boards.wait_fw_upgrade(on_progress=lambda s: print(s.overall, "%"))
```

## Errors

Six kinds, all deriving from `dt5215.Dt5215Error`:

| | |
|---|---|
| `ConnectionError` | the instrument cannot be reached |
| `Timeout` | the request went out and no reply came in time |
| `DeviceError` | the instrument refused: `result: false`, or a 4xx/5xx |
| `AuthError` | 401: no token, wrong credentials, expired token |
| `NotSupported` | this firmware does not have the endpoint |
| `InvalidArgument` | a parameter is wrong — **nothing was sent** |

```python
try:
    dev.boards.write_register(0, 1, 0x1000, 0x42)
except dt5215.InvalidArgument as e:
    print("my mistake, in parameter", e.name)
except dt5215.DeviceError as e:
    print("the instrument said no:", e.reason)
```

## Typed results, and the fields the SDK does not know yet

Every result is a dataclass, so the editor can complete it and `mypy` can check it. Every
result also keeps the dictionary it came from:

```python
info = dev.info()
info.network.ten_giga.mtu          # typed
info.raw["info"]["sysversion"]     # anything, including a field added by a newer firmware
```

## HTTPS

The instrument serves plain HTTP today; HTTPS is coming and the SDK side is ready:

```python
dev = dt5215.connect("https://dt5215.lan", username="user", password="user",
                     tls=dt5215.Tls(ca_file="/etc/ssl/ni-ca.pem"))
```

`Tls` carries the CA bundle, a client certificate for mutual TLS, and `insecure=True` for a
self-signed certificate on a closed network. The default port follows the scheme and
verification is **on** by default. No operation knows which scheme it is on, so nothing else
changes the day the firmware serves TLS.

## From the shell

The package installs a `dt5215` command:

```bash
dt5215 --host 192.168.50.125 info
dt5215 chains
dt5215 boards
dt5215 health
dt5215 rescan --log
dt5215 watch --interval 2
dt5215 get /get_pll
dt5215 post /set_board_sync '{"master_slave": 0, "clk_source": 1}'
```

`DT5215_HOST`, `DT5215_USER`, `DT5215_PASSWORD` and `DT5215_TOKEN` fill in what you leave out.

## Without an instrument

```bash
pip install dt5215-emulator
dt5215-emulator --port 8080
DT5215_HOST=127.0.0.1:8080 dt5215 info
```

The emulator answers the whole API, statefully, and reproduces the firmware's quirks on
purpose. It is what this SDK's own tests run against.

## Examples

`examples/` has twelve, numbered in the order they are worth reading, from
`01_connect_and_info.py` to `12_raw_endpoint.py`. They all take `DT5215_HOST`,
`DT5215_USER` and `DT5215_PASSWORD` from the environment, and they are part of the test
suite — an example that does not run is documentation that lies.

## Tests

```bash
pip install -e ".[test]"
pytest
```

They run against the emulator, so no instrument is needed.
