embeddedci 2.13.0__tar.gz → 2.13.2__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.
- {embeddedci-2.13.0 → embeddedci-2.13.2}/CHANGELOG.md +28 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/PKG-INFO +59 -13
- {embeddedci-2.13.0 → embeddedci-2.13.2}/README.md +58 -12
- {embeddedci-2.13.0 → embeddedci-2.13.2}/pyproject.toml +1 -1
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/__init__.py +7 -1
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/capabilities.py +5 -1
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/client.py +136 -13
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/constants.py +3 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/dsp.py +1 -1
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/errors.py +19 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/replay.py +1 -1
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/results.py +34 -2
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/transport/tcp.py +77 -7
- embeddedci-2.13.2/src/embeddedci/benchpod/transport/tls.py +294 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/waveforms.py +1 -1
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/api_surface.json +33 -5
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/e2e/e2e_helpers.py +8 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/e2e/test_e2e_pod.py +7 -5
- embeddedci-2.13.2/tests/test_capabilities_scope.py +21 -0
- embeddedci-2.13.2/tests/test_dac_path_default.py +83 -0
- embeddedci-2.13.2/tests/test_lan_tls.py +151 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/.gitignore +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/LICENSE +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/docs/event-uart-design.md +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/examples/README.md +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/examples/test_bmp280.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/__init__.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/can.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/capture.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/ci.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/cli_config.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/cli_login.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/cloud_auth.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/connection.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/control_loop.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/decode.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/discovery.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/flash.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/gpio.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/gps.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/i2c.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/lease.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/lowlevel.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/motor_emulator.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/power.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/protocol.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/pytest_plugin.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/sensor.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/server_api.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/spi.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/state.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/transport/__init__.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/transport/base.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/transport/cloud.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/transport/serial.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/transport/usblink.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/uart.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/benchpod/wiring.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/py.typed +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/src/embeddedci/upload_build.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/conftest.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/e2e/README.md +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/e2e/conftest.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/e2e/test_e2e_cloud.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/e2e/test_e2e_digital.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/e2e/test_e2e_dut.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/e2e/test_e2e_gpio.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/e2e/test_e2e_la13_14.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/e2e/test_e2e_rev3.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/e2e/test_e2e_spi.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/e2e/test_e2e_usb.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/examples/test_bmp280_hil.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/examples/test_can_ecu_demo.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/examples/test_can_loopback_hil.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/examples/test_smoke.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_adc_fft_hw.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_api_surface.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_can.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_capabilities.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_capture.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_capture_replay_hw.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_ci.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_client_v2.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_cloud.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_connection.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_control_loop.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_control_loop_hw.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_current_out_waveforms.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_dac_limits_fixture.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_dac_path_ranges.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_decode.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_decode_parity.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_docs_coverage.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_dsp.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_flash_bridge.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_flash_build.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_generate16.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_gpio.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_i2c_decode.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_i2c_parity.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_image_switching.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_integration.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_la_voltage_fixture.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_lease.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_motor_emulator.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_one_source.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_pins_bias.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_protocol.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_refusal_errors.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_replay.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_sensor.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_serial_autodetect.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_serial_text_console.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_spi.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_tcp_transport.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_timed_power.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_timing.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_transport_failures.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_trigger_power.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_uart_capture.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_uart_session.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_unsupported_feature.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_upload_build.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_usb_net.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_waveforms.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/test_wiring.py +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/testdata/i2c_decode_vectors.json +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/testdata/spi_decode_vectors.json +0 -0
- {embeddedci-2.13.0 → embeddedci-2.13.2}/tests/testdata/uart_decode_vectors.json +0 -0
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog — `embeddedci`
|
|
2
2
|
|
|
3
|
+
## 2.13.2
|
|
4
|
+
|
|
5
|
+
- Docs: `calibrate()` corrects every ADC source from firmware 3.9.3 (they share the front end) and
|
|
6
|
+
the pod accepts an offset up to 150 mV. On the LAN, `capture_adc` volts use the fit the pod now
|
|
7
|
+
reports in `status`, so they include the pod's calibrated offset.
|
|
8
|
+
- The LAN API over TLS (pod firmware 3.9.3+): a network address on the default port connects to
|
|
9
|
+
8443 with TLS when the pod has it and falls back to plaintext 8080 only when 8443 refuses and
|
|
10
|
+
nothing is pinned. The pod's certificate is pinned on first use in `known_pods.json`, shared with
|
|
11
|
+
`benchpod-cli`; a different one raises `TlsPinMismatchError`. `BENCHPOD_TLS` (auto/on/off) and
|
|
12
|
+
`BENCHPOD_TLS_CA` (verify against a CA instead). New: `BenchPod.lan_tls()`,
|
|
13
|
+
`set_lan_tls_https_only()`, `regenerate_lan_tls()`, `set_lan_tls_cert()`, `LanTls`,
|
|
14
|
+
`trust_lan_tls()`, `forget_lan_tls()`, `TcpTransport.last_tls`, `TcpTransport.command_sequence()`.
|
|
15
|
+
The transport keeps the last TLS session per pod, so later connections resume in ~35 ms instead
|
|
16
|
+
of the pod's ~0.46 s full handshake.
|
|
17
|
+
The 8443 probe waits 1.5 s, past the host's 1 s SYN retransmit (a pod can leave the first SYN
|
|
18
|
+
unanswered), and a probe that times out is remembered like a refusal, so a pod without LAN TLS
|
|
19
|
+
costs one probe per process instead of 0.5 s on every command.
|
|
20
|
+
|
|
21
|
+
## 2.13.1
|
|
22
|
+
|
|
23
|
+
- `Capabilities.scope` is False on the digital-only board. Current firmware lists `signal` on every
|
|
24
|
+
board; it only stands for the scope on firmware without the `analog` flag.
|
|
25
|
+
- `generate`, `replay`, `Waveforms.preview` and `replay_waveform` (for a waveform stored without
|
|
26
|
+
a path) default to `dac_path="3v3"` instead of `"5v"`, so a call that leaves the output out can
|
|
27
|
+
no longer put 5 V on a 3.3 V pin. Pass `dac_path="5v"` to keep the old output. With
|
|
28
|
+
`route=False` (a loopback set up with `analog_path`), `generate` and `replay` scale for the
|
|
29
|
+
output this connection last routed (`cal1` = 5 V, `cal2` = 12 V), else `5v` as before.
|
|
30
|
+
|
|
3
31
|
## 2.13.0
|
|
4
32
|
|
|
5
33
|
- `generate()` on firmware 3.9.1+ (`Capabilities.generate16`) sends the volts (or microamps on
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: embeddedci
|
|
3
|
-
Version: 2.13.
|
|
3
|
+
Version: 2.13.2
|
|
4
4
|
Summary: BenchPod SDK and pytest plugin for EmbeddedCI hardware-in-the-loop testing
|
|
5
5
|
Project-URL: Homepage, https://embeddedci.com
|
|
6
6
|
Project-URL: Repository, https://github.com/embeddedci-com/embeddedci-python
|
|
@@ -292,6 +292,36 @@ power moved to the network too; see the compatibility table above.)
|
|
|
292
292
|
| `replay()` / client-side `replay_waveform()` (streams the waveform to the pod) | yes | yes | no | yes |
|
|
293
293
|
| Device lease | — | — | — | yes |
|
|
294
294
|
|
|
295
|
+
### TLS on the LAN
|
|
296
|
+
|
|
297
|
+
Pods with firmware 3.9.3 or later also serve the API on port 8443 with TLS. For a network address
|
|
298
|
+
on the default port (`192.168.1.5`, `:8080`, a pod name) the SDK connects to 8443 first and falls
|
|
299
|
+
back to plaintext 8080 only when the pod has no TLS port and nothing is pinned for it, so older
|
|
300
|
+
pods keep working. `:8443` means TLS only; any other port and the USB network adapter (`usb`, a
|
|
301
|
+
physical link) stay plaintext. `BENCHPOD_TLS=on|off` overrides (`auto` is the default).
|
|
302
|
+
`bp.transport.last_tls` says which one the last connection used.
|
|
303
|
+
|
|
304
|
+
The pod's certificate is its own, so the SDK pins its SHA-256 the first time (trust on first use)
|
|
305
|
+
in `known_pods.json` next to `benchpod-cli`'s config; the CLI reads and writes the same file. A
|
|
306
|
+
different certificate later raises `TlsPinMismatchError` (`addr`, `pinned`, `got`). After a
|
|
307
|
+
deliberate change elsewhere, `trust_lan_tls(host, sha256=None)` pins the certificate the pod
|
|
308
|
+
presents now (`sha256` only accepts that one) and `forget_lan_tls(host)` drops the pin. With
|
|
309
|
+
`BENCHPOD_TLS_CA=ca.pem` the certificate and host name are verified against that CA instead (for a
|
|
310
|
+
certificate you installed, signed by your own CA).
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
bp = BenchPod("192.168.1.220")
|
|
314
|
+
info = bp.lan_tls() # LanTls: port, https_only, sessions, subject, san, sha256, ...
|
|
315
|
+
bp.set_lan_tls_https_only(True) # plaintext 8080 off on Ethernet and Wi-Fi (USB keeps it)
|
|
316
|
+
bp.set_lan_tls_cert(open("pod.pem").read(), open("pod.key").read())
|
|
317
|
+
bp.regenerate_lan_tls()
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`set_lan_tls_https_only(False)`, `regenerate_lan_tls()` and `set_lan_tls_cert()` need a TLS, USB or
|
|
321
|
+
cloud connection; over plaintext Ethernet or Wi-Fi the pod refuses (`PodLockedError`).
|
|
322
|
+
`set_lan_tls_cert` needs a network connection (it uploads the bundle); after it and after
|
|
323
|
+
`regenerate_lan_tls()` the SDK pins the new certificate for the address it used.
|
|
324
|
+
|
|
295
325
|
### Environment variables
|
|
296
326
|
|
|
297
327
|
| Variable | Used when | Purpose |
|
|
@@ -304,6 +334,8 @@ power moved to the network too; see the compatibility table above.)
|
|
|
304
334
|
| `BENCHPOD_WIRING` | no `wiring` argument / `--benchpod-wiring` / `benchpod_wiring` fixture | wiring profile file (`.json`/`.toml`), see [Wiring profile](#wiring-profile) |
|
|
305
335
|
| `BENCHPOD_WIRING_SOURCE` | no `wiring_source` argument | `local` never asks embeddedci.com for the pod's stored wiring profile; `auto` (default) does, see [Wiring profile](#wiring-profile) |
|
|
306
336
|
| `BENCHPOD_LIFT_DAC_LIMITS` | pytest sessions | `1` clears the pod's DAC output limits for the session and restores the same limits at the end, even when tests fail (only with the output stage switched off) |
|
|
337
|
+
| `BENCHPOD_TLS` | network connections | `auto` (default), `on` or `off`: TLS to the pod's LAN API on 8443, see [TLS on the LAN](#tls-on-the-lan) |
|
|
338
|
+
| `BENCHPOD_TLS_CA` | network connections over TLS | PEM file of CA certificates: verify the pod's certificate and host name instead of pinning it |
|
|
307
339
|
| `BENCHPOD_STALL_TIMEOUT` | flashing | seconds with no SWD traffic before a flash attempt is aborted and retried (default 60) |
|
|
308
340
|
|
|
309
341
|
## API conventions and stability
|
|
@@ -989,11 +1021,12 @@ microvolt resolution (about 76 µV a step on `"5v"`) and `code16` is the 16-bit
|
|
|
989
1021
|
stays the nearest 8-bit level. On older firmware `code16` is `None` and the steps are 8-bit. `adc_read` routes the source (`"ext"`, `"cal1"`, `"cal2"`, `"current_in"`) and returns one averaged,
|
|
990
1022
|
calibrated reading; the pod refuses it while the input is still moving (e.g. a DAC left running).
|
|
991
1023
|
|
|
992
|
-
### Calibrating the
|
|
1024
|
+
### Calibrating the ADC offset
|
|
993
1025
|
|
|
994
1026
|
`"current_in"` reads the 4-20 mA terminal (J8) across a 249 Ω resistor, so 4 mA is 0.996 V and 20 mA is
|
|
995
|
-
4.98 V. Each pod
|
|
996
|
-
|
|
1027
|
+
4.98 V. Each pod's analog input has its own offset, from part tolerances: anywhere from a few mV
|
|
1028
|
+
to about 50 mV. With J8 open the terminal is at 0 V, so the pod can measure that offset itself and
|
|
1029
|
+
keep it in its flash:
|
|
997
1030
|
|
|
998
1031
|
```python
|
|
999
1032
|
cal = bp.calibrate() # disconnect J8 first. Calibration(source, calibrated, offset, a, b, ...)
|
|
@@ -1006,10 +1039,15 @@ bp.clear_calibration() # back to the built-in fit
|
|
|
1006
1039
|
```
|
|
1007
1040
|
|
|
1008
1041
|
Calibrate once per pod: it survives a reboot and a firmware update. The pod refuses
|
|
1009
|
-
(`BenchPodError`) when something is driving J8 (more than
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1042
|
+
(`BenchPodError`) when something is driving J8 (more than 150 mV; 50 mV before firmware 3.9.3)
|
|
1043
|
+
and keeps what it had. `a` and `b` are the fit the pod now uses for `current_in`
|
|
1044
|
+
(`volts = a + b * count`).
|
|
1045
|
+
|
|
1046
|
+
Every ADC source shares the same front end after its input switch, so from firmware 3.9.3 the pod
|
|
1047
|
+
takes the offset out of all of them: `adc_read` on `ext`, `cal1` and `cal2`, and `capture_adc`,
|
|
1048
|
+
whose `volts` use the fit the pod reports (on the LAN in `status`, through embeddedci.com in its
|
|
1049
|
+
capabilities). Older firmware corrects `current_in` only. Only the offset is calibrated; the gain
|
|
1050
|
+
uses the built-in fits. Needs firmware 3.4.0 (capability `calibrate`, `Capabilities.calibrate`).
|
|
1013
1051
|
|
|
1014
1052
|
### The 4-20 mA output
|
|
1015
1053
|
|
|
@@ -1176,7 +1214,12 @@ v35+ (`Capabilities.capture_trigger`).
|
|
|
1176
1214
|
|
|
1177
1215
|
Every DAC output keeps running until stopped. The returned `DacHandle` / `ReplayHandle` is a
|
|
1178
1216
|
context manager that stops it on exit; `bp.dac_stop()` stops any output (generator, replay or
|
|
1179
|
-
control loop). DAC paths are `"3v3"`, `"5v"` and `"12v"`.
|
|
1217
|
+
control loop). DAC paths are `"3v3"`, `"5v"` and `"12v"`. `generate`, `replay` and
|
|
1218
|
+
`replay_waveform` (for a waveform stored without a path) default to `"3v3"`, the lowest voltage
|
|
1219
|
+
output: pass the output your DUT pin tolerates, since `"5v"` and `"12v"` can drive more than a
|
|
1220
|
+
3.3 V pin takes. With `route=False` nothing is switched and the path only scales the volts, so the
|
|
1221
|
+
default is the output this connection last routed (`analog_path("cal1")` is the 5 V DAC,
|
|
1222
|
+
`"cal2"` the 12 V one), else `"5v"`.
|
|
1180
1223
|
|
|
1181
1224
|
```python
|
|
1182
1225
|
from embeddedci.benchpod import Fault
|
|
@@ -1203,11 +1246,11 @@ with bp.replay(ramp, dac_path="3v3", sample_rate_hz=10_000,
|
|
|
1203
1246
|
print(r.samples, r.sample_rate_hz, r.deep)
|
|
1204
1247
|
```
|
|
1205
1248
|
|
|
1206
|
-
* `generate(waveform, *, freq_hz, amplitude, offset=None, dac_path=
|
|
1249
|
+
* `generate(waveform, *, freq_hz, amplitude, offset=None, dac_path=None, duration=None,
|
|
1207
1250
|
sample_rate_hz=None, on_capture=False)` — `sine`, `square` or `sawtooth`. `amplitude` is the peak
|
|
1208
|
-
and `offset` the
|
|
1251
|
+
and `offset` the center (default: mid-range of the path). The firmware builds the waveform from
|
|
1209
1252
|
8-bit levels, so volts are quantised to full-scale / 255.
|
|
1210
|
-
* `replay(source, *, dac_path=
|
|
1253
|
+
* `replay(source, *, dac_path=None, mapping="faithful", sample_rate_hz=None, deep=None, fault=None,
|
|
1211
1254
|
are_codes=False, on_capture=False)` — `source` is a `Capture` (its volts, at its own sample rate
|
|
1212
1255
|
by default), a sequence of volts, or raw DAC codes with `are_codes=True`. `mapping="faithful"`
|
|
1213
1256
|
reproduces the voltage (clipping outside the path's range), `"fit"` auto-scales. Above 2048 samples
|
|
@@ -1534,6 +1577,7 @@ Invalid arguments raise `ValueError` before anything is sent. Everything else ra
|
|
|
1534
1577
|
| `TransportError` | the pod or tunnel could not be reached / talked to | |
|
|
1535
1578
|
| `TransportTimeout` (a `TransportError` and a `TimeoutError`) | the pod or tunnel did not answer in time | |
|
|
1536
1579
|
| `ConnectionClosedError` (a `TransportError`) | the connection ended or reset before the reply | |
|
|
1580
|
+
| `TlsPinMismatchError` (a `TransportError`) | the pod's TLS certificate is not the one pinned for it (see [TLS on the LAN](#tls-on-the-lan)) | `addr`, `pinned`, `got` |
|
|
1537
1581
|
| `FirmwareError` | the pod replied with an error (e.g. "la voltage not set"); a fix the firmware spells as a raw protocol command is rewritten into the SDK call in the message, and `firmware_message` keeps the pod's own text | `firmware_message`, `cmd` |
|
|
1538
1582
|
| `FlashError` | OpenOCD failed, is missing, or lacks the TCP backend | |
|
|
1539
1583
|
| `TargetUnreachableError` (a `FlashError`) | the probe worked but no target answered on SWD | |
|
|
@@ -1578,7 +1622,7 @@ lease_wait=600.0, lease_ttl=120, wiring=None)`. A context manager; `close()` is
|
|
|
1578
1622
|
|
|
1579
1623
|
| Area | Methods and properties |
|
|
1580
1624
|
|---|---|
|
|
1581
|
-
| Connection and device | `ping()`, `status()`, `capabilities`, `refresh_capabilities()` (drop the cached capabilities and read them again), `close()`, `leased` (true while this client holds the cloud lease), `wiring`, `save_wiring(wiring=None)` (store a cloud device's profile on embeddedci.com), `signal(name)`, `fpga_image(image)`, `usb_cc()`, `usb_network(on=None)` (the USB network setting; `False` turns the adapter off over the USB link) |
|
|
1625
|
+
| Connection and device | `ping()`, `status()`, `capabilities`, `refresh_capabilities()` (drop the cached capabilities and read them again), `close()`, `leased` (true while this client holds the cloud lease), `wiring`, `save_wiring(wiring=None)` (store a cloud device's profile on embeddedci.com), `signal(name)`, `fpga_image(image)`, `usb_cc()`, `usb_network(on=None)` (the USB network setting; `False` turns the adapter off over the USB link), `lan_tls()`, `set_lan_tls_https_only(on)`, `regenerate_lan_tls()`, `set_lan_tls_cert(cert_pem, key_pem)` (the LAN API over TLS, see [TLS on the LAN](#tls-on-the-lan)) |
|
|
1582
1626
|
| LA voltage | `set_la_voltage(volts)`, `get_la_voltage()` |
|
|
1583
1627
|
| Power and reset | `power_on(efuse=None, *, delay=None)`, `power_off(efuse=None, *, delay=None)`, `target_power(efuse=None, *, on, delay=None)`, `target_status()`, `power_status()`, `measure_power(duration, *, efuse=None, rate_hz=500.0, keep_samples=0)`, `power_profile(*, efuse=None, rate_hz=500.0, keep_samples=4096, max_duration=60.0)`, `reset_target(*, pulse=0.1)`, `set_reset(asserted)`, `reset_state()` |
|
|
1584
1628
|
| Flashing | `flash(...)`, `spi_flash(image, addr=0, *, erase=True, verify=True, hold_reset=False, sck=, mosi=, miso=, cs=)`, `open_spi(...)`, `open_motor_emulator(...)` |
|
|
@@ -1610,6 +1654,7 @@ few built from other shapes); `to_dict()` goes the other way where a type is sen
|
|
|
1610
1654
|
| `PowerProfile` | `measure_power`, `PowerProfileSession.result` | see [Power profiles](#power-profiles) |
|
|
1611
1655
|
| `PowerProfileSession` | `power_profile` (a context manager) | `start()`, `stop()`, `status()`, `result` |
|
|
1612
1656
|
| `ResetState` | `reset_target`, `set_reset`, `reset_state` | `asserted`, `supported` |
|
|
1657
|
+
| `LanTls` | `lan_tls`, `set_lan_tls_https_only`, `regenerate_lan_tls`, `set_lan_tls_cert` | `port`, `https_only`, `sessions`, `origin` (`generated`/`user`), `key`, `subject`, `san`, `not_after`, `sha256` (what clients pin), `error` (no certificate) |
|
|
1613
1658
|
| `UsbCcStatus` | `usb_cc` | `orientation`, `advertised`, `advertised_current` (A), `cc1_voltage`, `cc2_voltage` (V) |
|
|
1614
1659
|
| `PullState` | `pull_state`, `set_pull` | `la`, `enabled`, `direction`, `ohms`, `available` |
|
|
1615
1660
|
| `LaPinState` | `la_pins`, `configure_gpio` | `la`, `function`, `gpio`, `level`, `pull`, `pull_ohms`, `pull_on`, `in_use` |
|
|
@@ -1710,6 +1755,7 @@ timeout=60.0, lease_id=None)`. Errors raise `ServerApiError` (`status`), and an
|
|
|
1710
1755
|
|---|---|
|
|
1711
1756
|
| `parse_connection(raw)` | parse a connection string into a `ConnSpec` (raises `ConnectionConfigError`) |
|
|
1712
1757
|
| `resolve_connection(connection=None)` | the same, falling back to `BENCHPOD_CONNECTION` |
|
|
1758
|
+
| `trust_lan_tls(host, port=8080, *, sha256=None)`, `forget_lan_tls(host, port=8080)` | pin the TLS certificate the pod presents now (returns its SHA-256), or drop the pin (see [TLS on the LAN](#tls-on-the-lan)) |
|
|
1713
1759
|
| `make_build_reporter(*, api_base=None, audience=None, target="", name="")` | a `BuildReporter` inside GitHub Actions, else a `NoopBuildReporter` |
|
|
1714
1760
|
| `build_panel_curve(voc_code, sharpness=4.0, points=256)`, `build_linear_curve(max_code, rising=True, points=256)`, `build_constant_curve(value_code, points=256)` | control-loop curves (see [In-fabric DAC control loop](#in-fabric-dac-control-loop)) |
|
|
1715
1761
|
| `curve_output_at(curve, input_code)`, `input_percent_to_code(percent)` | evaluate a curve the way the gateware does; an input percentage as a code |
|
|
@@ -254,6 +254,36 @@ power moved to the network too; see the compatibility table above.)
|
|
|
254
254
|
| `replay()` / client-side `replay_waveform()` (streams the waveform to the pod) | yes | yes | no | yes |
|
|
255
255
|
| Device lease | — | — | — | yes |
|
|
256
256
|
|
|
257
|
+
### TLS on the LAN
|
|
258
|
+
|
|
259
|
+
Pods with firmware 3.9.3 or later also serve the API on port 8443 with TLS. For a network address
|
|
260
|
+
on the default port (`192.168.1.5`, `:8080`, a pod name) the SDK connects to 8443 first and falls
|
|
261
|
+
back to plaintext 8080 only when the pod has no TLS port and nothing is pinned for it, so older
|
|
262
|
+
pods keep working. `:8443` means TLS only; any other port and the USB network adapter (`usb`, a
|
|
263
|
+
physical link) stay plaintext. `BENCHPOD_TLS=on|off` overrides (`auto` is the default).
|
|
264
|
+
`bp.transport.last_tls` says which one the last connection used.
|
|
265
|
+
|
|
266
|
+
The pod's certificate is its own, so the SDK pins its SHA-256 the first time (trust on first use)
|
|
267
|
+
in `known_pods.json` next to `benchpod-cli`'s config; the CLI reads and writes the same file. A
|
|
268
|
+
different certificate later raises `TlsPinMismatchError` (`addr`, `pinned`, `got`). After a
|
|
269
|
+
deliberate change elsewhere, `trust_lan_tls(host, sha256=None)` pins the certificate the pod
|
|
270
|
+
presents now (`sha256` only accepts that one) and `forget_lan_tls(host)` drops the pin. With
|
|
271
|
+
`BENCHPOD_TLS_CA=ca.pem` the certificate and host name are verified against that CA instead (for a
|
|
272
|
+
certificate you installed, signed by your own CA).
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
bp = BenchPod("192.168.1.220")
|
|
276
|
+
info = bp.lan_tls() # LanTls: port, https_only, sessions, subject, san, sha256, ...
|
|
277
|
+
bp.set_lan_tls_https_only(True) # plaintext 8080 off on Ethernet and Wi-Fi (USB keeps it)
|
|
278
|
+
bp.set_lan_tls_cert(open("pod.pem").read(), open("pod.key").read())
|
|
279
|
+
bp.regenerate_lan_tls()
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`set_lan_tls_https_only(False)`, `regenerate_lan_tls()` and `set_lan_tls_cert()` need a TLS, USB or
|
|
283
|
+
cloud connection; over plaintext Ethernet or Wi-Fi the pod refuses (`PodLockedError`).
|
|
284
|
+
`set_lan_tls_cert` needs a network connection (it uploads the bundle); after it and after
|
|
285
|
+
`regenerate_lan_tls()` the SDK pins the new certificate for the address it used.
|
|
286
|
+
|
|
257
287
|
### Environment variables
|
|
258
288
|
|
|
259
289
|
| Variable | Used when | Purpose |
|
|
@@ -266,6 +296,8 @@ power moved to the network too; see the compatibility table above.)
|
|
|
266
296
|
| `BENCHPOD_WIRING` | no `wiring` argument / `--benchpod-wiring` / `benchpod_wiring` fixture | wiring profile file (`.json`/`.toml`), see [Wiring profile](#wiring-profile) |
|
|
267
297
|
| `BENCHPOD_WIRING_SOURCE` | no `wiring_source` argument | `local` never asks embeddedci.com for the pod's stored wiring profile; `auto` (default) does, see [Wiring profile](#wiring-profile) |
|
|
268
298
|
| `BENCHPOD_LIFT_DAC_LIMITS` | pytest sessions | `1` clears the pod's DAC output limits for the session and restores the same limits at the end, even when tests fail (only with the output stage switched off) |
|
|
299
|
+
| `BENCHPOD_TLS` | network connections | `auto` (default), `on` or `off`: TLS to the pod's LAN API on 8443, see [TLS on the LAN](#tls-on-the-lan) |
|
|
300
|
+
| `BENCHPOD_TLS_CA` | network connections over TLS | PEM file of CA certificates: verify the pod's certificate and host name instead of pinning it |
|
|
269
301
|
| `BENCHPOD_STALL_TIMEOUT` | flashing | seconds with no SWD traffic before a flash attempt is aborted and retried (default 60) |
|
|
270
302
|
|
|
271
303
|
## API conventions and stability
|
|
@@ -951,11 +983,12 @@ microvolt resolution (about 76 µV a step on `"5v"`) and `code16` is the 16-bit
|
|
|
951
983
|
stays the nearest 8-bit level. On older firmware `code16` is `None` and the steps are 8-bit. `adc_read` routes the source (`"ext"`, `"cal1"`, `"cal2"`, `"current_in"`) and returns one averaged,
|
|
952
984
|
calibrated reading; the pod refuses it while the input is still moving (e.g. a DAC left running).
|
|
953
985
|
|
|
954
|
-
### Calibrating the
|
|
986
|
+
### Calibrating the ADC offset
|
|
955
987
|
|
|
956
988
|
`"current_in"` reads the 4-20 mA terminal (J8) across a 249 Ω resistor, so 4 mA is 0.996 V and 20 mA is
|
|
957
|
-
4.98 V. Each pod
|
|
958
|
-
|
|
989
|
+
4.98 V. Each pod's analog input has its own offset, from part tolerances: anywhere from a few mV
|
|
990
|
+
to about 50 mV. With J8 open the terminal is at 0 V, so the pod can measure that offset itself and
|
|
991
|
+
keep it in its flash:
|
|
959
992
|
|
|
960
993
|
```python
|
|
961
994
|
cal = bp.calibrate() # disconnect J8 first. Calibration(source, calibrated, offset, a, b, ...)
|
|
@@ -968,10 +1001,15 @@ bp.clear_calibration() # back to the built-in fit
|
|
|
968
1001
|
```
|
|
969
1002
|
|
|
970
1003
|
Calibrate once per pod: it survives a reboot and a firmware update. The pod refuses
|
|
971
|
-
(`BenchPodError`) when something is driving J8 (more than
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1004
|
+
(`BenchPodError`) when something is driving J8 (more than 150 mV; 50 mV before firmware 3.9.3)
|
|
1005
|
+
and keeps what it had. `a` and `b` are the fit the pod now uses for `current_in`
|
|
1006
|
+
(`volts = a + b * count`).
|
|
1007
|
+
|
|
1008
|
+
Every ADC source shares the same front end after its input switch, so from firmware 3.9.3 the pod
|
|
1009
|
+
takes the offset out of all of them: `adc_read` on `ext`, `cal1` and `cal2`, and `capture_adc`,
|
|
1010
|
+
whose `volts` use the fit the pod reports (on the LAN in `status`, through embeddedci.com in its
|
|
1011
|
+
capabilities). Older firmware corrects `current_in` only. Only the offset is calibrated; the gain
|
|
1012
|
+
uses the built-in fits. Needs firmware 3.4.0 (capability `calibrate`, `Capabilities.calibrate`).
|
|
975
1013
|
|
|
976
1014
|
### The 4-20 mA output
|
|
977
1015
|
|
|
@@ -1138,7 +1176,12 @@ v35+ (`Capabilities.capture_trigger`).
|
|
|
1138
1176
|
|
|
1139
1177
|
Every DAC output keeps running until stopped. The returned `DacHandle` / `ReplayHandle` is a
|
|
1140
1178
|
context manager that stops it on exit; `bp.dac_stop()` stops any output (generator, replay or
|
|
1141
|
-
control loop). DAC paths are `"3v3"`, `"5v"` and `"12v"`.
|
|
1179
|
+
control loop). DAC paths are `"3v3"`, `"5v"` and `"12v"`. `generate`, `replay` and
|
|
1180
|
+
`replay_waveform` (for a waveform stored without a path) default to `"3v3"`, the lowest voltage
|
|
1181
|
+
output: pass the output your DUT pin tolerates, since `"5v"` and `"12v"` can drive more than a
|
|
1182
|
+
3.3 V pin takes. With `route=False` nothing is switched and the path only scales the volts, so the
|
|
1183
|
+
default is the output this connection last routed (`analog_path("cal1")` is the 5 V DAC,
|
|
1184
|
+
`"cal2"` the 12 V one), else `"5v"`.
|
|
1142
1185
|
|
|
1143
1186
|
```python
|
|
1144
1187
|
from embeddedci.benchpod import Fault
|
|
@@ -1165,11 +1208,11 @@ with bp.replay(ramp, dac_path="3v3", sample_rate_hz=10_000,
|
|
|
1165
1208
|
print(r.samples, r.sample_rate_hz, r.deep)
|
|
1166
1209
|
```
|
|
1167
1210
|
|
|
1168
|
-
* `generate(waveform, *, freq_hz, amplitude, offset=None, dac_path=
|
|
1211
|
+
* `generate(waveform, *, freq_hz, amplitude, offset=None, dac_path=None, duration=None,
|
|
1169
1212
|
sample_rate_hz=None, on_capture=False)` — `sine`, `square` or `sawtooth`. `amplitude` is the peak
|
|
1170
|
-
and `offset` the
|
|
1213
|
+
and `offset` the center (default: mid-range of the path). The firmware builds the waveform from
|
|
1171
1214
|
8-bit levels, so volts are quantised to full-scale / 255.
|
|
1172
|
-
* `replay(source, *, dac_path=
|
|
1215
|
+
* `replay(source, *, dac_path=None, mapping="faithful", sample_rate_hz=None, deep=None, fault=None,
|
|
1173
1216
|
are_codes=False, on_capture=False)` — `source` is a `Capture` (its volts, at its own sample rate
|
|
1174
1217
|
by default), a sequence of volts, or raw DAC codes with `are_codes=True`. `mapping="faithful"`
|
|
1175
1218
|
reproduces the voltage (clipping outside the path's range), `"fit"` auto-scales. Above 2048 samples
|
|
@@ -1496,6 +1539,7 @@ Invalid arguments raise `ValueError` before anything is sent. Everything else ra
|
|
|
1496
1539
|
| `TransportError` | the pod or tunnel could not be reached / talked to | |
|
|
1497
1540
|
| `TransportTimeout` (a `TransportError` and a `TimeoutError`) | the pod or tunnel did not answer in time | |
|
|
1498
1541
|
| `ConnectionClosedError` (a `TransportError`) | the connection ended or reset before the reply | |
|
|
1542
|
+
| `TlsPinMismatchError` (a `TransportError`) | the pod's TLS certificate is not the one pinned for it (see [TLS on the LAN](#tls-on-the-lan)) | `addr`, `pinned`, `got` |
|
|
1499
1543
|
| `FirmwareError` | the pod replied with an error (e.g. "la voltage not set"); a fix the firmware spells as a raw protocol command is rewritten into the SDK call in the message, and `firmware_message` keeps the pod's own text | `firmware_message`, `cmd` |
|
|
1500
1544
|
| `FlashError` | OpenOCD failed, is missing, or lacks the TCP backend | |
|
|
1501
1545
|
| `TargetUnreachableError` (a `FlashError`) | the probe worked but no target answered on SWD | |
|
|
@@ -1540,7 +1584,7 @@ lease_wait=600.0, lease_ttl=120, wiring=None)`. A context manager; `close()` is
|
|
|
1540
1584
|
|
|
1541
1585
|
| Area | Methods and properties |
|
|
1542
1586
|
|---|---|
|
|
1543
|
-
| Connection and device | `ping()`, `status()`, `capabilities`, `refresh_capabilities()` (drop the cached capabilities and read them again), `close()`, `leased` (true while this client holds the cloud lease), `wiring`, `save_wiring(wiring=None)` (store a cloud device's profile on embeddedci.com), `signal(name)`, `fpga_image(image)`, `usb_cc()`, `usb_network(on=None)` (the USB network setting; `False` turns the adapter off over the USB link) |
|
|
1587
|
+
| Connection and device | `ping()`, `status()`, `capabilities`, `refresh_capabilities()` (drop the cached capabilities and read them again), `close()`, `leased` (true while this client holds the cloud lease), `wiring`, `save_wiring(wiring=None)` (store a cloud device's profile on embeddedci.com), `signal(name)`, `fpga_image(image)`, `usb_cc()`, `usb_network(on=None)` (the USB network setting; `False` turns the adapter off over the USB link), `lan_tls()`, `set_lan_tls_https_only(on)`, `regenerate_lan_tls()`, `set_lan_tls_cert(cert_pem, key_pem)` (the LAN API over TLS, see [TLS on the LAN](#tls-on-the-lan)) |
|
|
1544
1588
|
| LA voltage | `set_la_voltage(volts)`, `get_la_voltage()` |
|
|
1545
1589
|
| Power and reset | `power_on(efuse=None, *, delay=None)`, `power_off(efuse=None, *, delay=None)`, `target_power(efuse=None, *, on, delay=None)`, `target_status()`, `power_status()`, `measure_power(duration, *, efuse=None, rate_hz=500.0, keep_samples=0)`, `power_profile(*, efuse=None, rate_hz=500.0, keep_samples=4096, max_duration=60.0)`, `reset_target(*, pulse=0.1)`, `set_reset(asserted)`, `reset_state()` |
|
|
1546
1590
|
| Flashing | `flash(...)`, `spi_flash(image, addr=0, *, erase=True, verify=True, hold_reset=False, sck=, mosi=, miso=, cs=)`, `open_spi(...)`, `open_motor_emulator(...)` |
|
|
@@ -1572,6 +1616,7 @@ few built from other shapes); `to_dict()` goes the other way where a type is sen
|
|
|
1572
1616
|
| `PowerProfile` | `measure_power`, `PowerProfileSession.result` | see [Power profiles](#power-profiles) |
|
|
1573
1617
|
| `PowerProfileSession` | `power_profile` (a context manager) | `start()`, `stop()`, `status()`, `result` |
|
|
1574
1618
|
| `ResetState` | `reset_target`, `set_reset`, `reset_state` | `asserted`, `supported` |
|
|
1619
|
+
| `LanTls` | `lan_tls`, `set_lan_tls_https_only`, `regenerate_lan_tls`, `set_lan_tls_cert` | `port`, `https_only`, `sessions`, `origin` (`generated`/`user`), `key`, `subject`, `san`, `not_after`, `sha256` (what clients pin), `error` (no certificate) |
|
|
1575
1620
|
| `UsbCcStatus` | `usb_cc` | `orientation`, `advertised`, `advertised_current` (A), `cc1_voltage`, `cc2_voltage` (V) |
|
|
1576
1621
|
| `PullState` | `pull_state`, `set_pull` | `la`, `enabled`, `direction`, `ohms`, `available` |
|
|
1577
1622
|
| `LaPinState` | `la_pins`, `configure_gpio` | `la`, `function`, `gpio`, `level`, `pull`, `pull_ohms`, `pull_on`, `in_use` |
|
|
@@ -1672,6 +1717,7 @@ timeout=60.0, lease_id=None)`. Errors raise `ServerApiError` (`status`), and an
|
|
|
1672
1717
|
|---|---|
|
|
1673
1718
|
| `parse_connection(raw)` | parse a connection string into a `ConnSpec` (raises `ConnectionConfigError`) |
|
|
1674
1719
|
| `resolve_connection(connection=None)` | the same, falling back to `BENCHPOD_CONNECTION` |
|
|
1720
|
+
| `trust_lan_tls(host, port=8080, *, sha256=None)`, `forget_lan_tls(host, port=8080)` | pin the TLS certificate the pod presents now (returns its SHA-256), or drop the pin (see [TLS on the LAN](#tls-on-the-lan)) |
|
|
1675
1721
|
| `make_build_reporter(*, api_base=None, audience=None, target="", name="")` | a `BuildReporter` inside GitHub Actions, else a `NoopBuildReporter` |
|
|
1676
1722
|
| `build_panel_curve(voc_code, sharpness=4.0, points=256)`, `build_linear_curve(max_code, rising=True, points=256)`, `build_constant_curve(value_code, points=256)` | control-loop curves (see [In-fabric DAC control loop](#in-fabric-dac-control-loop)) |
|
|
1677
1723
|
| `curve_output_at(curve, input_code)`, `input_percent_to_code(percent)` | evaluate a curve the way the gateware does; an input percentage as a code |
|
|
@@ -95,6 +95,7 @@ from .errors import (
|
|
|
95
95
|
PodLockedError,
|
|
96
96
|
PullConflictError,
|
|
97
97
|
TargetUnreachableError,
|
|
98
|
+
TlsPinMismatchError,
|
|
98
99
|
TransportError,
|
|
99
100
|
TransportTimeout,
|
|
100
101
|
TriggerTimeout,
|
|
@@ -109,7 +110,8 @@ from .lease import DeviceLease
|
|
|
109
110
|
from .lowlevel import LowLevel
|
|
110
111
|
from .power import PowerProfile, PowerProfileSession
|
|
111
112
|
from .replay import DacHandle, Fault, ReplayHandle, Segment
|
|
112
|
-
from .results import Capture, CorrelatedCapture, LaCapture, Trigger
|
|
113
|
+
from .results import Capture, CorrelatedCapture, LaCapture, LanTls, Trigger
|
|
114
|
+
from .transport.tls import delete_pin as forget_lan_tls, trust_lan_tls
|
|
113
115
|
from .server_api import ServerApi, ServerApiError, ServerPermissionDeniedError
|
|
114
116
|
from .state import (
|
|
115
117
|
AdcReading,
|
|
@@ -190,6 +192,10 @@ __all__ = [
|
|
|
190
192
|
"dsp",
|
|
191
193
|
"Capabilities",
|
|
192
194
|
"Capture",
|
|
195
|
+
"LanTls",
|
|
196
|
+
"TlsPinMismatchError",
|
|
197
|
+
"trust_lan_tls",
|
|
198
|
+
"forget_lan_tls",
|
|
193
199
|
"LaCapture",
|
|
194
200
|
"CorrelatedCapture",
|
|
195
201
|
"Trigger",
|
|
@@ -285,7 +285,11 @@ class Capabilities:
|
|
|
285
285
|
caps = status.get("caps")
|
|
286
286
|
if isinstance(caps, (list, tuple)):
|
|
287
287
|
names = {str(x).lower() for x in caps}
|
|
288
|
-
|
|
288
|
+
# "signal" stood for the scope on firmware that predates the analog flag. Current
|
|
289
|
+
# firmware lists it on every board, the digital-only one included, and names "scope"
|
|
290
|
+
# itself, so it only counts when the status has no analog flag.
|
|
291
|
+
legacy = _as_bool(status, "analog") is None
|
|
292
|
+
c.scope = c.scope or "scope" in names or (legacy and "signal" in names)
|
|
289
293
|
c.analyzer = c.analyzer or ("la" in names or "analyzer" in names)
|
|
290
294
|
c.serial = c.serial or ("uart" in names or "serial" in names)
|
|
291
295
|
# The DAC/gateware features. Firmware advertises these in status.caps[] built from
|