dt5215 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dt5215-1.0.1/.gitignore +51 -0
- dt5215-1.0.1/LICENSE +21 -0
- dt5215-1.0.1/PKG-INFO +187 -0
- dt5215-1.0.1/README.md +164 -0
- dt5215-1.0.1/examples/01_connect_and_info.py +40 -0
- dt5215-1.0.1/examples/02_chains_and_boards.py +42 -0
- dt5215-1.0.1/examples/03_enumerate.py +42 -0
- dt5215-1.0.1/examples/04_registers.py +35 -0
- dt5215-1.0.1/examples/05_hv.py +34 -0
- dt5215-1.0.1/examples/06_board_firmware_upgrade.py +73 -0
- dt5215-1.0.1/examples/07_io_config.py +43 -0
- dt5215-1.0.1/examples/08_clock_and_ddmtd.py +47 -0
- dt5215-1.0.1/examples/09_time_and_gps.py +40 -0
- dt5215-1.0.1/examples/10_monitoring_loop.py +65 -0
- dt5215-1.0.1/examples/11_configuration_backup.py +36 -0
- dt5215-1.0.1/examples/12_raw_endpoint.py +35 -0
- dt5215-1.0.1/examples/_common.py +25 -0
- dt5215-1.0.1/pyproject.toml +43 -0
- dt5215-1.0.1/src/dt5215/__init__.py +48 -0
- dt5215-1.0.1/src/dt5215/_codec.py +143 -0
- dt5215-1.0.1/src/dt5215/_version.py +3 -0
- dt5215-1.0.1/src/dt5215/cli.py +323 -0
- dt5215-1.0.1/src/dt5215/device.py +284 -0
- dt5215-1.0.1/src/dt5215/enums.py +126 -0
- dt5215-1.0.1/src/dt5215/errors.py +92 -0
- dt5215-1.0.1/src/dt5215/groups.py +786 -0
- dt5215-1.0.1/src/dt5215/transport.py +484 -0
- dt5215-1.0.1/src/dt5215/types.py +1034 -0
- dt5215-1.0.1/tests/conftest.py +35 -0
- dt5215-1.0.1/tests/test_boards.py +138 -0
- dt5215-1.0.1/tests/test_chains.py +147 -0
- dt5215-1.0.1/tests/test_cli.py +78 -0
- dt5215-1.0.1/tests/test_connect.py +126 -0
- dt5215-1.0.1/tests/test_device.py +131 -0
- dt5215-1.0.1/tests/test_errors.py +73 -0
- dt5215-1.0.1/tests/test_examples.py +52 -0
- dt5215-1.0.1/tests/test_io_clock_time.py +176 -0
- dt5215-1.0.1/tests/test_network_system.py +136 -0
- dt5215-1.0.1/tests/test_spec.py +29 -0
dt5215-1.0.1/.gitignore
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# build outputs
|
|
2
|
+
output/
|
|
3
|
+
site/
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
.ci-build/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.pyc
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
|
|
14
|
+
# C / C++
|
|
15
|
+
sdk/c/build/
|
|
16
|
+
sdk/cpp/build/
|
|
17
|
+
*.o
|
|
18
|
+
*.a
|
|
19
|
+
*.so
|
|
20
|
+
*.so.*
|
|
21
|
+
*.dll
|
|
22
|
+
*.lib
|
|
23
|
+
*.exe
|
|
24
|
+
|
|
25
|
+
# Go
|
|
26
|
+
sdk/go/build/
|
|
27
|
+
|
|
28
|
+
# .NET
|
|
29
|
+
sdk/dotnet/**/bin/
|
|
30
|
+
sdk/dotnet/**/obj/
|
|
31
|
+
|
|
32
|
+
# Node
|
|
33
|
+
node_modules/
|
|
34
|
+
sdk/node/dist/
|
|
35
|
+
|
|
36
|
+
# Java
|
|
37
|
+
sdk/java/target/
|
|
38
|
+
.m2/
|
|
39
|
+
|
|
40
|
+
# conformance
|
|
41
|
+
ci-test/report/
|
|
42
|
+
|
|
43
|
+
# editors
|
|
44
|
+
.vscode/
|
|
45
|
+
.idea/
|
|
46
|
+
*.swp
|
|
47
|
+
|
|
48
|
+
# generated by the build and packaging scripts
|
|
49
|
+
tars/
|
|
50
|
+
packaging/windows/staging/
|
|
51
|
+
staging/
|
dt5215-1.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nuclear Instruments SRL
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
dt5215-1.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dt5215
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Control SDK of the Nuclear Instruments / CAEN DT5215 FERS concentrator: HTTP/JSON over Ethernet
|
|
5
|
+
Project-URL: Homepage, https://www.nuclearinstruments.eu
|
|
6
|
+
Project-URL: Documentation, https://downloads.nuclearinstruments.eu/package/dt5215-control-sdk
|
|
7
|
+
Project-URL: Source, https://gitlab.nuclearinstruments.eu/public-repo/fers/dt5215-control-sdk
|
|
8
|
+
Author: Nuclear Instruments
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: caen,concentrator,daq,dt5215,fers,nuclear instruments,scintillator,sipm,tdlink
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
17
|
+
Classifier: Topic :: System :: Monitoring
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Provides-Extra: test
|
|
20
|
+
Requires-Dist: dt5215-emulator; extra == 'test'
|
|
21
|
+
Requires-Dist: pytest>=7; extra == 'test'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# DT5215 Control SDK — Python
|
|
25
|
+
|
|
26
|
+
The control and monitoring API of the **DT5215** FERS concentrator, in Python, with no
|
|
27
|
+
dependencies: the standard library is enough.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install dt5215
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import dt5215
|
|
35
|
+
|
|
36
|
+
with dt5215.connect("192.168.0.10", username="user", password="user") as dev:
|
|
37
|
+
info = dev.info()
|
|
38
|
+
print(info.hostname, info.info.sw_release)
|
|
39
|
+
|
|
40
|
+
for chain in dev.chains.status().enabled:
|
|
41
|
+
print("chain", chain.chain, chain.board_count, "boards",
|
|
42
|
+
"%.1f Mbps" % chain.mbps)
|
|
43
|
+
|
|
44
|
+
for board in dev.chains.links().boards:
|
|
45
|
+
print(board.id, board.name, board.tempBoard, "C", board.hv_Vmon, "V")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Python 3.8 and later, on Linux, Windows and macOS.
|
|
49
|
+
|
|
50
|
+
## What is in it
|
|
51
|
+
|
|
52
|
+
Everything the instrument's web interface can do, grouped the way the API is grouped:
|
|
53
|
+
|
|
54
|
+
| | |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `dev.info()` `dev.alive()` `dev.error_flags()` `dev.leds()` `dev.reboot()` | identity, health, front panel, life cycle |
|
|
57
|
+
| `dev.chains` | the 8 TDlink chains: status, enumeration, the boards on them, heatmaps |
|
|
58
|
+
| `dev.boards` | the FERS boards: registers, HV, firmware upgrade over TDlink |
|
|
59
|
+
| `dev.io` | the front panel: LEMO direction and function, sync matrix, test pulse |
|
|
60
|
+
| `dev.clock` | the master PLL, the DDMTD phase detectors, the link phase |
|
|
61
|
+
| `dev.network` | the LAN, 10 GbE and USB interfaces, and their traffic |
|
|
62
|
+
| `dev.time` | the GPS receiver, the system clock, systemd-timesyncd |
|
|
63
|
+
| `dev.system` | the log, the debug counters, the configuration file |
|
|
64
|
+
| `dev.raw` | any endpoint, including one a newer firmware adds before this SDK names it |
|
|
65
|
+
|
|
66
|
+
Event data is **not** here: it leaves the instrument over the binary streaming sockets that
|
|
67
|
+
Janus and FERSlib speak. This SDK is the control plane.
|
|
68
|
+
|
|
69
|
+
## Three things worth knowing
|
|
70
|
+
|
|
71
|
+
**The firmware answers HTTP 200 with `result: false`.** A client that only reads the status
|
|
72
|
+
code believes every refusal succeeded. This SDK checks the body and raises `DeviceError`
|
|
73
|
+
with the instrument's own message — including the configuration validator's list of
|
|
74
|
+
complaints, which is otherwise hidden behind "configuration validation failed".
|
|
75
|
+
|
|
76
|
+
**Tokens expire** (7 days), and restarting the bridge application throws them all away. Pass
|
|
77
|
+
`username`/`password` and the SDK logs in when it needs to, notices a 401, logs in again and
|
|
78
|
+
retries the request once. `dev.alive()` is the one operation that answers without a token:
|
|
79
|
+
call it to tell "the instrument is down" from "my token is stale".
|
|
80
|
+
|
|
81
|
+
**Some operations take minutes.** `chains.rescan()` runs the whole enumeration and blocks
|
|
82
|
+
until it is over. To follow it instead:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
dev.chains.rescan_async()
|
|
86
|
+
status = dev.chains.wait_enumeration(on_progress=lambda s: print(s.progress, "%"))
|
|
87
|
+
print(dev.chains.enumeration_log())
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The same shape works for the board firmware upgrade:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
info = dev.boards.fw_upload("firmware.ffu") # what the file holds, and for which models
|
|
94
|
+
dev.boards.fw_start(dev.chains.links().boards) # Board, BoardRef, (chain, node) or "0:1"
|
|
95
|
+
status = dev.boards.wait_fw_upgrade(on_progress=lambda s: print(s.overall, "%"))
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Errors
|
|
99
|
+
|
|
100
|
+
Six kinds, all deriving from `dt5215.Dt5215Error`:
|
|
101
|
+
|
|
102
|
+
| | |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `ConnectionError` | the instrument cannot be reached |
|
|
105
|
+
| `Timeout` | the request went out and no reply came in time |
|
|
106
|
+
| `DeviceError` | the instrument refused: `result: false`, or a 4xx/5xx |
|
|
107
|
+
| `AuthError` | 401: no token, wrong credentials, expired token |
|
|
108
|
+
| `NotSupported` | this firmware does not have the endpoint |
|
|
109
|
+
| `InvalidArgument` | a parameter is wrong — **nothing was sent** |
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
try:
|
|
113
|
+
dev.boards.write_register(0, 1, 0x1000, 0x42)
|
|
114
|
+
except dt5215.InvalidArgument as e:
|
|
115
|
+
print("my mistake, in parameter", e.name)
|
|
116
|
+
except dt5215.DeviceError as e:
|
|
117
|
+
print("the instrument said no:", e.reason)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Typed results, and the fields the SDK does not know yet
|
|
121
|
+
|
|
122
|
+
Every result is a dataclass, so the editor can complete it and `mypy` can check it. Every
|
|
123
|
+
result also keeps the dictionary it came from:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
info = dev.info()
|
|
127
|
+
info.network.ten_giga.mtu # typed
|
|
128
|
+
info.raw["info"]["sysversion"] # anything, including a field added by a newer firmware
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## HTTPS
|
|
132
|
+
|
|
133
|
+
The instrument serves plain HTTP today; HTTPS is coming and the SDK side is ready:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
dev = dt5215.connect("https://dt5215.lan", username="user", password="user",
|
|
137
|
+
tls=dt5215.Tls(ca_file="/etc/ssl/ni-ca.pem"))
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`Tls` carries the CA bundle, a client certificate for mutual TLS, and `insecure=True` for a
|
|
141
|
+
self-signed certificate on a closed network. The default port follows the scheme and
|
|
142
|
+
verification is **on** by default. No operation knows which scheme it is on, so nothing else
|
|
143
|
+
changes the day the firmware serves TLS.
|
|
144
|
+
|
|
145
|
+
## From the shell
|
|
146
|
+
|
|
147
|
+
The package installs a `dt5215` command:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
dt5215 --host 192.168.0.10 info
|
|
151
|
+
dt5215 chains
|
|
152
|
+
dt5215 boards
|
|
153
|
+
dt5215 health
|
|
154
|
+
dt5215 rescan --log
|
|
155
|
+
dt5215 watch --interval 2
|
|
156
|
+
dt5215 get /get_pll
|
|
157
|
+
dt5215 post /set_board_sync '{"master_slave": 0, "clk_source": 1}'
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`DT5215_HOST`, `DT5215_USER`, `DT5215_PASSWORD` and `DT5215_TOKEN` fill in what you leave out.
|
|
161
|
+
|
|
162
|
+
## Without an instrument
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
pip install dt5215-emulator
|
|
166
|
+
dt5215-emulator --port 8080
|
|
167
|
+
DT5215_HOST=127.0.0.1:8080 dt5215 info
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The emulator answers the whole API, statefully, and reproduces the firmware's quirks on
|
|
171
|
+
purpose. It is what this SDK's own tests run against.
|
|
172
|
+
|
|
173
|
+
## Examples
|
|
174
|
+
|
|
175
|
+
`examples/` has twelve, numbered in the order they are worth reading, from
|
|
176
|
+
`01_connect_and_info.py` to `12_raw_endpoint.py`. They all take `DT5215_HOST`,
|
|
177
|
+
`DT5215_USER` and `DT5215_PASSWORD` from the environment, and they are part of the test
|
|
178
|
+
suite — an example that does not run is documentation that lies.
|
|
179
|
+
|
|
180
|
+
## Tests
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
pip install -e ".[test]"
|
|
184
|
+
pytest
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
They run against the emulator, so no instrument is needed.
|
dt5215-1.0.1/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# DT5215 Control SDK — Python
|
|
2
|
+
|
|
3
|
+
The control and monitoring API of the **DT5215** FERS concentrator, in Python, with no
|
|
4
|
+
dependencies: the standard library is enough.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pip install dt5215
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
import dt5215
|
|
12
|
+
|
|
13
|
+
with dt5215.connect("192.168.0.10", username="user", password="user") as dev:
|
|
14
|
+
info = dev.info()
|
|
15
|
+
print(info.hostname, info.info.sw_release)
|
|
16
|
+
|
|
17
|
+
for chain in dev.chains.status().enabled:
|
|
18
|
+
print("chain", chain.chain, chain.board_count, "boards",
|
|
19
|
+
"%.1f Mbps" % chain.mbps)
|
|
20
|
+
|
|
21
|
+
for board in dev.chains.links().boards:
|
|
22
|
+
print(board.id, board.name, board.tempBoard, "C", board.hv_Vmon, "V")
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Python 3.8 and later, on Linux, Windows and macOS.
|
|
26
|
+
|
|
27
|
+
## What is in it
|
|
28
|
+
|
|
29
|
+
Everything the instrument's web interface can do, grouped the way the API is grouped:
|
|
30
|
+
|
|
31
|
+
| | |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `dev.info()` `dev.alive()` `dev.error_flags()` `dev.leds()` `dev.reboot()` | identity, health, front panel, life cycle |
|
|
34
|
+
| `dev.chains` | the 8 TDlink chains: status, enumeration, the boards on them, heatmaps |
|
|
35
|
+
| `dev.boards` | the FERS boards: registers, HV, firmware upgrade over TDlink |
|
|
36
|
+
| `dev.io` | the front panel: LEMO direction and function, sync matrix, test pulse |
|
|
37
|
+
| `dev.clock` | the master PLL, the DDMTD phase detectors, the link phase |
|
|
38
|
+
| `dev.network` | the LAN, 10 GbE and USB interfaces, and their traffic |
|
|
39
|
+
| `dev.time` | the GPS receiver, the system clock, systemd-timesyncd |
|
|
40
|
+
| `dev.system` | the log, the debug counters, the configuration file |
|
|
41
|
+
| `dev.raw` | any endpoint, including one a newer firmware adds before this SDK names it |
|
|
42
|
+
|
|
43
|
+
Event data is **not** here: it leaves the instrument over the binary streaming sockets that
|
|
44
|
+
Janus and FERSlib speak. This SDK is the control plane.
|
|
45
|
+
|
|
46
|
+
## Three things worth knowing
|
|
47
|
+
|
|
48
|
+
**The firmware answers HTTP 200 with `result: false`.** A client that only reads the status
|
|
49
|
+
code believes every refusal succeeded. This SDK checks the body and raises `DeviceError`
|
|
50
|
+
with the instrument's own message — including the configuration validator's list of
|
|
51
|
+
complaints, which is otherwise hidden behind "configuration validation failed".
|
|
52
|
+
|
|
53
|
+
**Tokens expire** (7 days), and restarting the bridge application throws them all away. Pass
|
|
54
|
+
`username`/`password` and the SDK logs in when it needs to, notices a 401, logs in again and
|
|
55
|
+
retries the request once. `dev.alive()` is the one operation that answers without a token:
|
|
56
|
+
call it to tell "the instrument is down" from "my token is stale".
|
|
57
|
+
|
|
58
|
+
**Some operations take minutes.** `chains.rescan()` runs the whole enumeration and blocks
|
|
59
|
+
until it is over. To follow it instead:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
dev.chains.rescan_async()
|
|
63
|
+
status = dev.chains.wait_enumeration(on_progress=lambda s: print(s.progress, "%"))
|
|
64
|
+
print(dev.chains.enumeration_log())
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The same shape works for the board firmware upgrade:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
info = dev.boards.fw_upload("firmware.ffu") # what the file holds, and for which models
|
|
71
|
+
dev.boards.fw_start(dev.chains.links().boards) # Board, BoardRef, (chain, node) or "0:1"
|
|
72
|
+
status = dev.boards.wait_fw_upgrade(on_progress=lambda s: print(s.overall, "%"))
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Errors
|
|
76
|
+
|
|
77
|
+
Six kinds, all deriving from `dt5215.Dt5215Error`:
|
|
78
|
+
|
|
79
|
+
| | |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `ConnectionError` | the instrument cannot be reached |
|
|
82
|
+
| `Timeout` | the request went out and no reply came in time |
|
|
83
|
+
| `DeviceError` | the instrument refused: `result: false`, or a 4xx/5xx |
|
|
84
|
+
| `AuthError` | 401: no token, wrong credentials, expired token |
|
|
85
|
+
| `NotSupported` | this firmware does not have the endpoint |
|
|
86
|
+
| `InvalidArgument` | a parameter is wrong — **nothing was sent** |
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
try:
|
|
90
|
+
dev.boards.write_register(0, 1, 0x1000, 0x42)
|
|
91
|
+
except dt5215.InvalidArgument as e:
|
|
92
|
+
print("my mistake, in parameter", e.name)
|
|
93
|
+
except dt5215.DeviceError as e:
|
|
94
|
+
print("the instrument said no:", e.reason)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Typed results, and the fields the SDK does not know yet
|
|
98
|
+
|
|
99
|
+
Every result is a dataclass, so the editor can complete it and `mypy` can check it. Every
|
|
100
|
+
result also keeps the dictionary it came from:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
info = dev.info()
|
|
104
|
+
info.network.ten_giga.mtu # typed
|
|
105
|
+
info.raw["info"]["sysversion"] # anything, including a field added by a newer firmware
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## HTTPS
|
|
109
|
+
|
|
110
|
+
The instrument serves plain HTTP today; HTTPS is coming and the SDK side is ready:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
dev = dt5215.connect("https://dt5215.lan", username="user", password="user",
|
|
114
|
+
tls=dt5215.Tls(ca_file="/etc/ssl/ni-ca.pem"))
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`Tls` carries the CA bundle, a client certificate for mutual TLS, and `insecure=True` for a
|
|
118
|
+
self-signed certificate on a closed network. The default port follows the scheme and
|
|
119
|
+
verification is **on** by default. No operation knows which scheme it is on, so nothing else
|
|
120
|
+
changes the day the firmware serves TLS.
|
|
121
|
+
|
|
122
|
+
## From the shell
|
|
123
|
+
|
|
124
|
+
The package installs a `dt5215` command:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
dt5215 --host 192.168.0.10 info
|
|
128
|
+
dt5215 chains
|
|
129
|
+
dt5215 boards
|
|
130
|
+
dt5215 health
|
|
131
|
+
dt5215 rescan --log
|
|
132
|
+
dt5215 watch --interval 2
|
|
133
|
+
dt5215 get /get_pll
|
|
134
|
+
dt5215 post /set_board_sync '{"master_slave": 0, "clk_source": 1}'
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`DT5215_HOST`, `DT5215_USER`, `DT5215_PASSWORD` and `DT5215_TOKEN` fill in what you leave out.
|
|
138
|
+
|
|
139
|
+
## Without an instrument
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
pip install dt5215-emulator
|
|
143
|
+
dt5215-emulator --port 8080
|
|
144
|
+
DT5215_HOST=127.0.0.1:8080 dt5215 info
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The emulator answers the whole API, statefully, and reproduces the firmware's quirks on
|
|
148
|
+
purpose. It is what this SDK's own tests run against.
|
|
149
|
+
|
|
150
|
+
## Examples
|
|
151
|
+
|
|
152
|
+
`examples/` has twelve, numbered in the order they are worth reading, from
|
|
153
|
+
`01_connect_and_info.py` to `12_raw_endpoint.py`. They all take `DT5215_HOST`,
|
|
154
|
+
`DT5215_USER` and `DT5215_PASSWORD` from the environment, and they are part of the test
|
|
155
|
+
suite — an example that does not run is documentation that lies.
|
|
156
|
+
|
|
157
|
+
## Tests
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
pip install -e ".[test]"
|
|
161
|
+
pytest
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
They run against the emulator, so no instrument is needed.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Connect, and print what the instrument is.
|
|
2
|
+
|
|
3
|
+
The first thing to run against a new unit: it proves the address, the credentials and the
|
|
4
|
+
firmware all line up.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from _common import open_device
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
with open_device() as dev:
|
|
12
|
+
alive = dev.alive()
|
|
13
|
+
print("instrument up for {} s (application instance {})".format(
|
|
14
|
+
alive.uptime_seconds, alive.instance_id))
|
|
15
|
+
|
|
16
|
+
info = dev.info()
|
|
17
|
+
print("hostname {}".format(info.hostname))
|
|
18
|
+
print("PID {} (PCB rev {})".format(info.pid, info.pcb_revision))
|
|
19
|
+
print("software {}".format(info.info.sw_release))
|
|
20
|
+
print("FPGA {}".format(info.info.fw_fpga_release))
|
|
21
|
+
print("logged in as {}".format(info.user))
|
|
22
|
+
|
|
23
|
+
print("\nnetwork")
|
|
24
|
+
print(" LAN {} (configured {}, dhcp {})".format(
|
|
25
|
+
info.network.lan.current_ip, info.network.lan.ip, info.network.lan.dhcp))
|
|
26
|
+
print(" 10 GbE {} (enabled {}, mtu {})".format(
|
|
27
|
+
info.network.ten_giga.current_ip, info.network.ten_giga.enable,
|
|
28
|
+
info.network.ten_giga.mtu))
|
|
29
|
+
|
|
30
|
+
print("\nsensors")
|
|
31
|
+
for sensor in info.temperatures:
|
|
32
|
+
print(" {:<12} {:>8.2f} {}".format(sensor.text or sensor.name,
|
|
33
|
+
sensor.value, sensor.unit))
|
|
34
|
+
|
|
35
|
+
flags = dev.error_flags()
|
|
36
|
+
print("\nhealth: {}".format("ok" if flags.ok else "FAULT"))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
main()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""What is connected: the eight TDlink chains and the boards on them."""
|
|
2
|
+
|
|
3
|
+
from _common import open_device
|
|
4
|
+
from dt5215 import ChainStatus
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
with open_device() as dev:
|
|
9
|
+
status = dev.chains.status()
|
|
10
|
+
print("chain state boards Mbps ev/s SFP")
|
|
11
|
+
for chain in status.chains:
|
|
12
|
+
state = getattr(ChainStatus.of(chain.chain_status), "name", "?").lower()
|
|
13
|
+
print(" {} {:<9} {:>6} {:>9.2f} {:>9.0f} {}".format(
|
|
14
|
+
chain.chain, state, chain.board_count, chain.mbps, chain.evps,
|
|
15
|
+
chain.sfp.vendor_pn or ("present" if chain.sfp.present else "-")))
|
|
16
|
+
if chain.los or chain.fault:
|
|
17
|
+
print(" loss of signal: {}, transmitter fault: {}".format(
|
|
18
|
+
chain.los, chain.fault))
|
|
19
|
+
|
|
20
|
+
print("\ntotal {:.2f} Mbps, event builder buffer {}%".format(
|
|
21
|
+
status.total_mbps, status.event_builder.get("output_buffer_status", "?")))
|
|
22
|
+
|
|
23
|
+
links = dev.chains.links()
|
|
24
|
+
print("\n{} board(s)".format(len(links.boards)))
|
|
25
|
+
for board in links.boards:
|
|
26
|
+
faults = ", ".join(board.status.faults) or "-"
|
|
27
|
+
print(" {:<6} {:<6} PID {:<7} fw {:<6} {:>5.1f} C HV {:>5.1f} V {}".format(
|
|
28
|
+
board.id, board.name, board.pid, board.fwVersion, board.tempBoard,
|
|
29
|
+
board.hv_Vmon, faults))
|
|
30
|
+
|
|
31
|
+
# the per-channel count map of the first board, as an 8-wide grid
|
|
32
|
+
if links.boards:
|
|
33
|
+
first = links.boards[0]
|
|
34
|
+
heatmap = dev.chains.heatmap(first.chain, first.node)
|
|
35
|
+
print("\nheatmap of {} ({} x {}):".format(heatmap.board, heatmap.rows,
|
|
36
|
+
heatmap.columns))
|
|
37
|
+
for row in heatmap.grid():
|
|
38
|
+
print(" " + " ".join("{:>6.0f}".format(v) for v in row))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
main()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Enumerate the chains and watch it happen.
|
|
2
|
+
|
|
3
|
+
``chains.rescan()`` blocks for minutes, so this starts it with ``rescan_async()`` and polls
|
|
4
|
+
the status — which is also how you drive a progress bar in a GUI.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from _common import open_device
|
|
8
|
+
from dt5215 import EnumPhase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
with open_device() as dev:
|
|
13
|
+
# enable the chains that are cabled; a chain left out of the mapping is disabled
|
|
14
|
+
dev.chains.set_enabled({0: True, 1: True})
|
|
15
|
+
|
|
16
|
+
print("starting the enumeration")
|
|
17
|
+
dev.chains.rescan_async()
|
|
18
|
+
|
|
19
|
+
last = ""
|
|
20
|
+
|
|
21
|
+
def show(status):
|
|
22
|
+
nonlocal last
|
|
23
|
+
phase = getattr(EnumPhase.of(status.phase), "name", "?").lower()
|
|
24
|
+
line = " attempt {} {:<12} {:>3.0f}% chain {}".format(
|
|
25
|
+
status.attempt, phase, status.progress,
|
|
26
|
+
status.current_chain if status.current_chain >= 0 else "-")
|
|
27
|
+
if line != last:
|
|
28
|
+
print(line)
|
|
29
|
+
last = line
|
|
30
|
+
|
|
31
|
+
status = dev.chains.wait_enumeration(timeout=300.0, poll=0.5, on_progress=show)
|
|
32
|
+
print("FAILED" if status.failed else "done")
|
|
33
|
+
|
|
34
|
+
print("\n--- enumeration log ---")
|
|
35
|
+
print(dev.chains.enumeration_log())
|
|
36
|
+
|
|
37
|
+
for link in dev.chains.links().links:
|
|
38
|
+
print("chain {}: {} board(s)".format(link.id, len(link.boards)))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
main()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Read and write a register of a FERS board over TDlink.
|
|
2
|
+
|
|
3
|
+
Register addresses are in ``FERS_Registers_520X.h`` of FERSlib; this reads one back after
|
|
4
|
+
writing it, which is the honest way to prove a link works end to end.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from _common import open_device
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
with open_device() as dev:
|
|
12
|
+
boards = dev.chains.links().boards
|
|
13
|
+
if not boards:
|
|
14
|
+
print("no boards: enumerate first (see 03_enumerate.py)")
|
|
15
|
+
return
|
|
16
|
+
|
|
17
|
+
board = boards[0]
|
|
18
|
+
print("talking to board {} ({})".format(board.id, board.name))
|
|
19
|
+
|
|
20
|
+
# 0x01000000 is a scratch register on the 520X family
|
|
21
|
+
address = 0x01000000
|
|
22
|
+
before = dev.boards.read_register(board.chain, board.node, address)
|
|
23
|
+
print(" read 0x{:08X} -> 0x{:08X}".format(address, before.value))
|
|
24
|
+
|
|
25
|
+
dev.boards.write_register(board.chain, board.node, address, 0xDEADBEEF)
|
|
26
|
+
after = dev.boards.read_register(board.chain, board.node, address)
|
|
27
|
+
print(" wrote 0xDEADBEEF, read back 0x{:08X}".format(after.value))
|
|
28
|
+
|
|
29
|
+
if before.value is not None:
|
|
30
|
+
dev.boards.write_register(board.chain, board.node, address, before.value)
|
|
31
|
+
print(" restored 0x{:08X}".format(before.value))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if __name__ == "__main__":
|
|
35
|
+
main()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Check the HV of every board and switch it off safely.
|
|
2
|
+
|
|
3
|
+
Switching all the HV off is the one operation worth having in a script you can run in a
|
|
4
|
+
hurry; it reports the boards that refused instead of raising, so you know which one is still
|
|
5
|
+
live.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from _common import open_device
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
with open_device() as dev:
|
|
13
|
+
status = dev.boards.hv_status()
|
|
14
|
+
print("system HV: {}{}{}".format(
|
|
15
|
+
"on" if status.hv_on else "off",
|
|
16
|
+
" OVERVOLTAGE" if status.hv_overvoltage else "",
|
|
17
|
+
" OVERCURRENT" if status.hv_overcurrent else ""))
|
|
18
|
+
|
|
19
|
+
for board in dev.chains.links().boards:
|
|
20
|
+
print(" {:<6} {:<6} HV {:<3} {:>6.1f} V {:>6.2f} mA det {:>5.1f} C".format(
|
|
21
|
+
board.id, board.name, "on" if board.hv_on else "off",
|
|
22
|
+
board.hv_Vmon, board.hv_Imon, board.tempDetector))
|
|
23
|
+
|
|
24
|
+
if status.hv_on:
|
|
25
|
+
print("\nswitching every HV off")
|
|
26
|
+
result = dev.boards.switch_off_all_hv()
|
|
27
|
+
print(" {} board(s) switched off".format(result.switched_off or 0))
|
|
28
|
+
for error in result.errors or []:
|
|
29
|
+
print(" REFUSED: chain {} board {} ({}), status {}".format(
|
|
30
|
+
error.chain, error.board, error.model, error.status))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
main()
|