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.
Files changed (39) hide show
  1. dt5215-1.0.1/.gitignore +51 -0
  2. dt5215-1.0.1/LICENSE +21 -0
  3. dt5215-1.0.1/PKG-INFO +187 -0
  4. dt5215-1.0.1/README.md +164 -0
  5. dt5215-1.0.1/examples/01_connect_and_info.py +40 -0
  6. dt5215-1.0.1/examples/02_chains_and_boards.py +42 -0
  7. dt5215-1.0.1/examples/03_enumerate.py +42 -0
  8. dt5215-1.0.1/examples/04_registers.py +35 -0
  9. dt5215-1.0.1/examples/05_hv.py +34 -0
  10. dt5215-1.0.1/examples/06_board_firmware_upgrade.py +73 -0
  11. dt5215-1.0.1/examples/07_io_config.py +43 -0
  12. dt5215-1.0.1/examples/08_clock_and_ddmtd.py +47 -0
  13. dt5215-1.0.1/examples/09_time_and_gps.py +40 -0
  14. dt5215-1.0.1/examples/10_monitoring_loop.py +65 -0
  15. dt5215-1.0.1/examples/11_configuration_backup.py +36 -0
  16. dt5215-1.0.1/examples/12_raw_endpoint.py +35 -0
  17. dt5215-1.0.1/examples/_common.py +25 -0
  18. dt5215-1.0.1/pyproject.toml +43 -0
  19. dt5215-1.0.1/src/dt5215/__init__.py +48 -0
  20. dt5215-1.0.1/src/dt5215/_codec.py +143 -0
  21. dt5215-1.0.1/src/dt5215/_version.py +3 -0
  22. dt5215-1.0.1/src/dt5215/cli.py +323 -0
  23. dt5215-1.0.1/src/dt5215/device.py +284 -0
  24. dt5215-1.0.1/src/dt5215/enums.py +126 -0
  25. dt5215-1.0.1/src/dt5215/errors.py +92 -0
  26. dt5215-1.0.1/src/dt5215/groups.py +786 -0
  27. dt5215-1.0.1/src/dt5215/transport.py +484 -0
  28. dt5215-1.0.1/src/dt5215/types.py +1034 -0
  29. dt5215-1.0.1/tests/conftest.py +35 -0
  30. dt5215-1.0.1/tests/test_boards.py +138 -0
  31. dt5215-1.0.1/tests/test_chains.py +147 -0
  32. dt5215-1.0.1/tests/test_cli.py +78 -0
  33. dt5215-1.0.1/tests/test_connect.py +126 -0
  34. dt5215-1.0.1/tests/test_device.py +131 -0
  35. dt5215-1.0.1/tests/test_errors.py +73 -0
  36. dt5215-1.0.1/tests/test_examples.py +52 -0
  37. dt5215-1.0.1/tests/test_io_clock_time.py +176 -0
  38. dt5215-1.0.1/tests/test_network_system.py +136 -0
  39. dt5215-1.0.1/tests/test_spec.py +29 -0
@@ -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()