plesty-lib 0.3.5.dev1__tar.gz → 0.3.5.dev3__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.
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/CHANGELOG.md +129 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/PKG-INFO +1 -1
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/base_device.md +6 -1
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/composite_device.md +51 -1
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/test_helper.md +25 -1
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/experiment.md +47 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/gl-secret-detection-report.json +5 -5
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/composite_device.py +400 -26
- plesty_lib-0.3.5.dev3/plesty/lib/device/device_utils.py +231 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/funcs.py +18 -2
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/experiment/__init__.py +3 -0
- plesty_lib-0.3.5.dev3/plesty/lib/experiment/assets/preflight.yaml +20 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/experiment/base_experiment.py +43 -3
- plesty_lib-0.3.5.dev3/plesty/lib/experiment/preflight.py +168 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/service/tcp_ip_client.py +25 -4
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/client_field_test.py +88 -9
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/field_test.py +330 -24
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/report_artifact.py +7 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/__init__.py +17 -1
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/apt.py +9 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/serial.py +5 -0
- plesty_lib-0.3.5.dev3/plesty/lib/traffic/serial_ports.py +86 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/qt/plot.py +2 -2
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/qt/shell.py +1 -1
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/utils/logger.py +54 -20
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_client_field_test.py +123 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_composite_config.py +280 -4
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_base.py +4 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_funcs.py +64 -1
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_experiment.py +30 -0
- plesty_lib-0.3.5.dev3/tests/test_experiment_preflight.py +114 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_field_test.py +250 -2
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_report_artifact.py +7 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_traffic_drivers.py +101 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_utils.py +36 -0
- plesty_lib-0.3.5.dev1/plesty/lib/device/device_utils.py +0 -108
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/.gitignore +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/.gitlab-ci.yml +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/LICENSE +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/LICENSES/LGPL-3.0-or-later.txt +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/README.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/REUSE.toml +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/analyzer.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/api_reference.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/architecture.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/contributing.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/data_schemas.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/cmd_solver.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/error_handling.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/func_system.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/index.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/logging_system.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/param_system.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/traffic_manager.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/imgs/device_layers.svg +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/imgs/device_standard.svg +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/imgs/plesty_framework.svg +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/index.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/monitor.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/quickstart.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/toc.yaml +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/ui.md +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/__init__.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/analyzer/__init__.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/analyzer/base_analyzer.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/data/__init__.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/data/array.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/data/ctype_manager.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/data/io.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/data/table.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/data/types.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/data/units.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/__init__.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/async_wrapper.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/base_apt_device.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/base_device_sync.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/base_tcp_scpi_device.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/base_visa_scpi_device.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/doc.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/params.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/telemetry.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/experiment/journal.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/experiment/runs.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/experiment/schedule.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/monitor/__init__.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/monitor/assets/viz.yaml +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/monitor/base_monitor.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/monitor/sources.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/monitor/viz.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/service/__init__.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/service/resource_manager.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/service/tcp_ip_server.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/sim/__init__.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/sim/apt.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/sim/data_generator.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/sim/demo_device.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/solver/__init__.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/solver/iceblock.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/solver/scpi.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/__init__.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/analyzer_pipeline.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/device_func_system.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/device_param_system.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/device_pipeline.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/experiment_pipeline.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/field_test_concurrency.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/grouped_param_keys.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/resource_allocation.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/schema_params.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/schema_refresh.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/apt_protocol.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/tcp_ip.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/usb_utils.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/utils.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/visa.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/__init__.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/assets/palette.json +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/assets/shell.qss +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/monitor_panel.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/panel.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/qt/__init__.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/qt/app.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/qt/recorder.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/theme.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/utils/__init__.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/utils/config.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/utils/dll_utils.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/utils/error_utils.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/utils/registry.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/utils/settings.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/pyproject.toml +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_analyzer.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_analyzer_pipeline.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_async_wrapper_threading.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_config.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_data_array.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_data_io_hdf5.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_data_types.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_demo_device.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_apt.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_data_path.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_params.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_pipeline.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_scpi.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_telemetry.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_experiment_pipeline.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_experiment_runs.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_misc.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_monitor.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_schema_params.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_schema_refresh.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_service_manager.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_settings.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_shipped_imports.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_sim.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_solver.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_tcp_ip_resources.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_traffic_orphan.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_traffic_utils.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_ui.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_visa_discovery.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_viz.py +0 -0
- {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/uv.lock +0 -0
|
@@ -1,5 +1,134 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.5.dev3 — 2026-08-18
|
|
4
|
+
|
|
5
|
+
Development pre-release for field-testing operation kinds (#39): operations
|
|
6
|
+
declare what they do and the field test drives a motion out and back, a
|
|
7
|
+
lifecycle op once, a control op at rest; plus the pol-pl rig findings #35
|
|
8
|
+
and #38. Same line as 0.3.5.dev2 — the final release is 0.4.0.
|
|
9
|
+
|
|
10
|
+
- **Operations declare what they do, and the field test drives them by
|
|
11
|
+
kind** (#39). `@expose_to_api(kind=…)` and a schema `kind` block record
|
|
12
|
+
one of `read | motion | acquire | lifecycle | control | configure`
|
|
13
|
+
(`plesty.lib.device.device_utils.OpKind`; `FuncDoc.kind`, the describe
|
|
14
|
+
payload and `function_docs()` carry it). `FieldTestPipeline` acts on it
|
|
15
|
+
instead of asking the developer per operation: a `motion` op is driven
|
|
16
|
+
one `step` out from what its `position_key` reports and put back (the
|
|
17
|
+
functions gate checks it came back; the stability gate repeats out and
|
|
18
|
+
back), a `lifecycle` op runs once when allow-listed and never on repeat,
|
|
19
|
+
a `control` op once at rest after the motions (skipped when nothing
|
|
20
|
+
moved), a `configure` op is never called. `op_kinds={…}` overrides the
|
|
21
|
+
module's declaration. On plesty-k10cr1 the operation every pol-pl row
|
|
22
|
+
calls could not be tested at all: `move_absolute(position)` was called
|
|
23
|
+
bare and failed on the missing argument.
|
|
24
|
+
- **The client tier drives operations by kind too.** `ClientFieldTest`
|
|
25
|
+
takes `op_kinds=` (the generated `field_test_client.py` passes the host
|
|
26
|
+
tier's): a motion is queried through `position_key`, moved one step out
|
|
27
|
+
and back over the wire as many pairs as the host tier's call count, a
|
|
28
|
+
lifecycle op once, a control op once after the motions, a configure op
|
|
29
|
+
never; a motion without `position_key`/`target`/`step` fails the gate.
|
|
30
|
+
- **A provocation that breaks is a failed gate, not a forced timeout.**
|
|
31
|
+
The drain and recovery gates called the slow op with the payload as one
|
|
32
|
+
positional dict (`getattr(device, slow_op)(dict(payload))`), so an
|
|
33
|
+
`@expose_to_api` slow op raised `TypeError` before any transport
|
|
34
|
+
traffic — and both gates counted the exception as an abandoned call and
|
|
35
|
+
passed. The call now goes through the operation's own call form, only a
|
|
36
|
+
timeout-class error counts as forced, and a motion slow op is a delta
|
|
37
|
+
from where the part is, moved back afterwards (`motion_restored` in the
|
|
38
|
+
evidence). The slow-op payload is prepared before the timeout is
|
|
39
|
+
shortened, so reading the start position cannot itself time out.
|
|
40
|
+
|
|
41
|
+
- **A serial port that is held says so** (#35). pyserial reports a wrong
|
|
42
|
+
port name and a port open in another process with the same
|
|
43
|
+
`SerialException`, and on the K10CR1 rig `Access is denied` on the right
|
|
44
|
+
`COM4` — Kinesis had the stage loaded — cost the session to driver and
|
|
45
|
+
VCP checks. `TrafficManager.open` now asks the subclass to explain a
|
|
46
|
+
failure (`_explain_open_error`, `None` by default) and appends the answer
|
|
47
|
+
to the raised message. `AptTrafficManager` and `SerialTrafficManager`
|
|
48
|
+
answer for EACCES/EBUSY: on Windows and for EBUSY, the port exists but is
|
|
49
|
+
open elsewhere — close Kinesis/APT (a loaded stage locks the FTDI device
|
|
50
|
+
via D2XX and blocks the VCP), stray interpreters, serial terminals, replug
|
|
51
|
+
to drop a stale handle; on Linux EACCES, add the user to `dialout`. The
|
|
52
|
+
port's `list_ports` description and hwid follow, so the user sees at once
|
|
53
|
+
whether the address is the intended device. Windows sets no `errno` on
|
|
54
|
+
the exception, so the number is read out of the message text. Other
|
|
55
|
+
failures (`ENOENT`, timeouts) are reported as before.
|
|
56
|
+
- `ModuleInfo.from_project` and the field-test version lookup no longer
|
|
57
|
+
answer for whatever repository `GIT_DIR` names: a git hook exports it for
|
|
58
|
+
the repository being pushed, and every `git -C <root>` in `report_artifact`
|
|
59
|
+
followed it — a field test run from a pre-push hook reported the hook's
|
|
60
|
+
remote and version, and the pre-push suite failed on the tmp-dir module
|
|
61
|
+
test. `GIT_*` is scrubbed from the subprocess environment.
|
|
62
|
+
|
|
63
|
+
- **Client-tier `functions` gate no longer demands a dict of every
|
|
64
|
+
operation** (#38). It required `isinstance(answer, dict)` for each
|
|
65
|
+
allow-listed operation, while the host tier asks that only of
|
|
66
|
+
schema-declared ones; on lightfield_spectrometer `acquire` (`None`) and
|
|
67
|
+
`get_recent_file` (`str`) came back 21 times each with no failure and
|
|
68
|
+
the gate still failed. A call that returns is the pass; the answer's
|
|
69
|
+
type is recorded in the evidence.
|
|
70
|
+
|
|
71
|
+
## 0.3.5.dev2 — 2026-08-17
|
|
72
|
+
|
|
73
|
+
Development pre-release for field-testing the rig start-up (#36, #37):
|
|
74
|
+
bounded, visible connects, the preflight, and default logging. Same line as
|
|
75
|
+
0.3.5.dev1 — the final release is 0.4.0.
|
|
76
|
+
|
|
77
|
+
- **A run starts only on a rig that is really there** (pol-pl field
|
|
78
|
+
finding: a sweep went on with the powermeter server absent).
|
|
79
|
+
`CompositeDevice.preflight()` checks that every remote sub-device exposes
|
|
80
|
+
the methods listed under the new `requires` config key and answers
|
|
81
|
+
`identity()`; `Experiment.setup()` runs it and raises `RigNotReadyError`
|
|
82
|
+
on any problem. `plesty.lib.experiment.connect_rig(build, optional=…)`
|
|
83
|
+
is the start-up for every experiment's `__main__`: build, preflight, and
|
|
84
|
+
on a failure print which device, at which address, from which variable,
|
|
85
|
+
then ask `[r]etry / [a]bort / [s]kip` (skip only for optional
|
|
86
|
+
sub-devices, returned as `skipped`); non-interactive processes get the
|
|
87
|
+
diagnosis logged and `RigNotReadyError`. Text in
|
|
88
|
+
`experiment/assets/preflight.yaml`.
|
|
89
|
+
- **A composite that is given up closes its clients.** New
|
|
90
|
+
`CompositeDevice.release()` closes every remote client on its owning
|
|
91
|
+
thread (server-side disconnect with a 1 s window, linger 0) and stops the
|
|
92
|
+
threads; `disconnect_all()` does the same for remote clients, and a first
|
|
93
|
+
connect that fails closes the clients that did connect. A client left to
|
|
94
|
+
the garbage collector blocked the interpreter in its ZMQ context's
|
|
95
|
+
`__del__` — seen mid-run after `connect_rig` rebuilt a rig without the
|
|
96
|
+
powermeter. `connect_rig` releases a rig it gives up.
|
|
97
|
+
- **A first connect to a dead server now returns.** The client's connect
|
|
98
|
+
failure path closed its DEALER socket with the default infinite LINGER,
|
|
99
|
+
so `ctx.term()` blocked forever on the undelivered handshake — a rig built
|
|
100
|
+
against one absent server hung with no output, and no attempt ladder or
|
|
101
|
+
deadline could bound it. `_DeviceTCPIPClient` now drops undelivered
|
|
102
|
+
messages (linger 0) when a connect fails and on `close()`.
|
|
103
|
+
`CompositeDevice` also resolves every address before starting any connect
|
|
104
|
+
(a half-configured `.env` raises `ValueError` without touching a server)
|
|
105
|
+
and stops its worker threads when a first connect fails.
|
|
106
|
+
- **`CompositeDevice` signals sub-device connection state and bounds the first
|
|
107
|
+
connect** (#36). Each sub-device carries a `ConnectionState`
|
|
108
|
+
(`CONNECTING → CONNECTED → DEGRADED → DISCONNECTED`), read with
|
|
109
|
+
`status()` / `status(dev)` and observed with `on_status_change(callback)`;
|
|
110
|
+
every transition is logged (WARNING for `DEGRADED` / `DISCONNECTED`).
|
|
111
|
+
New config key `connect_deadline_s` (default 10 s): a server that does not
|
|
112
|
+
answer within it raises `DeviceUnreachableError(dev, address, elapsed_s)`
|
|
113
|
+
from the constructor instead of running attempts × timeout × backoff
|
|
114
|
+
(~40 s per dead server before). Under thread affinity the first connects
|
|
115
|
+
run in parallel on the sub-devices' own threads. `reconnect()` reuses the
|
|
116
|
+
sub-device's configured `timeout_ms` / `attempts` / `backoff_s` instead of
|
|
117
|
+
the 50 s defaults; a call timeout marks the sub-device `DEGRADED` before
|
|
118
|
+
the rebuild. `_DeviceTCPIPClient.set_default_timeout(ms)` added.
|
|
119
|
+
- **Default logging for experiments and composites** (#37): nothing is
|
|
120
|
+
silent anymore without module code. `Experiment` logs
|
|
121
|
+
`Run <id>: N step(s) (k already completed), run dir …`, `Setup: …`,
|
|
122
|
+
`Step i/N <id> (<op>)` / `Step i/N done in x s`, `Teardown: …`, and
|
|
123
|
+
`Run … completed (N steps) in x s`. `CompositeDevice` logs
|
|
124
|
+
`Connecting <dev> @ <addr>` / `Connected <dev> @ <addr> in x s`, each failed
|
|
125
|
+
attempt with the retry delay, `Reconnecting <dev> @ <addr>`, and the timeout
|
|
126
|
+
a retried call ran under. New `plesty.lib.utils.logger.ensure_logging()`
|
|
127
|
+
(root level + one console sink, idempotent) is called by both constructors;
|
|
128
|
+
`setup_logging(app)` builds on it and adds the per-process log file — the
|
|
129
|
+
first `app` names the file, so a rig built before its experiment no longer
|
|
130
|
+
steals the file name.
|
|
131
|
+
|
|
3
132
|
## 0.3.5.dev1 — 2026-08-17
|
|
4
133
|
|
|
5
134
|
Development pre-release for field-testing the run convention (#34); the
|
|
@@ -261,7 +261,12 @@ model.standard_methods # the common device API (connect/write/query/reset/...)
|
|
|
261
261
|
`functions` unifies the two ways a device gains operations: schema-registered
|
|
262
262
|
operations (`source="schema"`) and `@expose_to_api` methods
|
|
263
263
|
(`source="custom"`); framework plumbing is kept out and surfaced separately
|
|
264
|
-
under `standard_methods` (`source="standard"`).
|
|
264
|
+
under `standard_methods` (`source="standard"`). Each `FuncDoc` also carries
|
|
265
|
+
its `kind` — what the operation does to the world (`read`, `motion`,
|
|
266
|
+
`acquire`, `lifecycle`, `control`, `configure`), declared with
|
|
267
|
+
`@expose_to_api(kind=..., position_key=..., step=...)` or a `"kind"` block in
|
|
268
|
+
`schema_func.json` and used by the field test to drive the operation
|
|
269
|
+
(see *test_helper*, "Kinds"). Per-parameter and return
|
|
265
270
|
descriptions are parsed from Google-style docstring `Args:`/`Returns:`
|
|
266
271
|
sections — write those in your device methods and the generated documentation
|
|
267
272
|
inherits them.
|
|
@@ -60,7 +60,9 @@ composite = CompositeDevice(
|
|
|
60
60
|
|---|---|
|
|
61
61
|
| `address` | Explicit ZMQ address, skipping the environment |
|
|
62
62
|
| `env` | Environment variable to read, overriding the derived name |
|
|
63
|
-
| `timeout_ms`, `attempts`, `backoff_s` | Forwarded to `connect_client` |
|
|
63
|
+
| `timeout_ms`, `attempts`, `backoff_s` | Forwarded to `connect_client` (first connect and every reconnect) |
|
|
64
|
+
| `connect_deadline_s` | Budget for the *first* connect (default 10 s); past it the constructor raises `DeviceUnreachableError` |
|
|
65
|
+
| `requires` | Remote methods the server must expose; checked by `preflight()` (a wrong address connects fine but is not this device) |
|
|
64
66
|
|
|
65
67
|
The address comes from `address` if given, else the environment; an unset
|
|
66
68
|
variable raises a `ValueError` naming the variable to set.
|
|
@@ -106,10 +108,48 @@ explicitly.
|
|
|
106
108
|
`connect_client(address)` is the module-level builder used for the rebuilds:
|
|
107
109
|
`build_client` plus a connection-handshake retry with linear backoff, which
|
|
108
110
|
matters when several clients open back-to-back against the same server.
|
|
111
|
+
`reconnect(dev)` rebuilds with the sub-device's own `timeout_ms` /
|
|
112
|
+
`attempts` / `backoff_s`, not the defaults.
|
|
109
113
|
|
|
110
114
|
Every multi-device composite gets this robustness for free instead of
|
|
111
115
|
re-implementing it per module.
|
|
112
116
|
|
|
117
|
+
## Connection state and a bounded first connect
|
|
118
|
+
|
|
119
|
+
The composite is never silent about its servers. Constructing it logs one
|
|
120
|
+
line per sub-device (`Connecting hwp @ tcp://… (timeout 5000 ms) …`,
|
|
121
|
+
`Connected hwp @ tcp://… in 0.4 s`, or `Connect to tcp://… failed, attempt
|
|
122
|
+
1/4: … ; retrying in 2 s`) and a summary (`PolPlRig ready in 1.2 s:
|
|
123
|
+
hwp=tcp://…, spec=tcp://…`) — the console sink is switched on by the
|
|
124
|
+
composite itself (`ensure_logging()`), so this shows even when the composite
|
|
125
|
+
is built before the experiment that would configure logging.
|
|
126
|
+
|
|
127
|
+
The **first connect is bounded**: each configured sub-device gets
|
|
128
|
+
`connect_deadline_s` (default 10 s); a server that does not answer within it
|
|
129
|
+
raises `DeviceUnreachableError(dev, address, elapsed_s)` from the constructor
|
|
130
|
+
instead of grinding through attempts × timeout × backoff. Under thread
|
|
131
|
+
affinity the first connects run in parallel on the sub-devices' own threads,
|
|
132
|
+
so three dead servers cost one deadline, not three. Reconnects during a run
|
|
133
|
+
keep the attempt ladder — a server busy finishing a long move must be waited
|
|
134
|
+
for, not declared dead.
|
|
135
|
+
|
|
136
|
+
Each sub-device carries a `ConnectionState` — `CONNECTING → CONNECTED →
|
|
137
|
+
DEGRADED` (a call timed out, the client is being rebuilt) `→ DISCONNECTED`
|
|
138
|
+
(connect/reconnect failed, or `disconnect_all()`):
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
composite.status() # {"hwp": ConnectionState.CONNECTED, "spec": ...}
|
|
142
|
+
composite.status("hwp") # ConnectionState.CONNECTED
|
|
143
|
+
|
|
144
|
+
@composite.on_status_change
|
|
145
|
+
def _show(dev, old, new, detail):
|
|
146
|
+
ui.set_led(dev, new) # runs on the thread that changed the state
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Every transition is logged (INFO, or WARNING for `DEGRADED` /
|
|
150
|
+
`DISCONNECTED`) whether or not anyone subscribes; a subscriber that raises
|
|
151
|
+
is logged and ignored — a broken UI hook must not break the rig.
|
|
152
|
+
|
|
113
153
|
## One thread per sub-device
|
|
114
154
|
|
|
115
155
|
ZMQ sockets are not thread-safe, and even migrating one between threads is
|
|
@@ -161,6 +201,16 @@ for a data directory either fails or means nothing:
|
|
|
161
201
|
composite.set_data_path(frames_dir, devices=["spec"])
|
|
162
202
|
```
|
|
163
203
|
|
|
204
|
+
## Preflight
|
|
205
|
+
|
|
206
|
+
`preflight()` proves each sub-device is the device it is supposed to be and
|
|
207
|
+
answers: a remote sub-device must expose every method in its `requires`
|
|
208
|
+
list and its `identity()` must reply; a local one must have the required
|
|
209
|
+
attributes. It returns `{name: problem}` for the failures (empty when the
|
|
210
|
+
rig is ready) and logs one line per sub-device either way. `Experiment`
|
|
211
|
+
runs it in `setup()`; `plesty.lib.experiment.connect_rig` runs it at
|
|
212
|
+
start-up and asks the operator what to do (see the experiment guide).
|
|
213
|
+
|
|
164
214
|
## Interplay with experiments
|
|
165
215
|
|
|
166
216
|
`Experiment` accepts a `CompositeDevice` at construction; its default
|
|
@@ -455,7 +455,7 @@ if __name__ == "__main__":
|
|
|
455
455
|
| 2 | `test_connect_lifecycle` | connect / identity / `check_errors` / disconnect / reconnect, each timed; identity stable across the reconnect |
|
|
456
456
|
| 3 | `test_param_roundtrip` | every writable parameter round-trips; the value read back is written back, so device state is unchanged |
|
|
457
457
|
| 4 | `test_param_constraints` | hardware MIN/MAX and option sets against the schema envelope — a schema wider than the hardware fails |
|
|
458
|
-
| 5 | `test_functions` | every operation on the safe allow-list
|
|
458
|
+
| 5 | `test_functions` | every operation on the safe allow-list can be called — by kind: a motion one step out and back, a lifecycle op once, a control op once at rest after the motions |
|
|
459
459
|
| 6 | `test_buffer_drain` | a query abandoned mid-flight must not leave its answer for the next query to consume |
|
|
460
460
|
| 7 | `test_error_recovery` | the device recovers from a forced timeout, and the first command after a reconnect succeeds |
|
|
461
461
|
| 8 | `test_stability` | every queryable parameter and safe op repeated `repetitions` times to collect the statistics |
|
|
@@ -464,6 +464,29 @@ if __name__ == "__main__":
|
|
|
464
464
|
(a `"safe": true` entry in `schema_func.json` is forwarded automatically). Without an allow-list
|
|
465
465
|
the function gate is skipped — no motion or emission operation ever runs unattended.
|
|
466
466
|
|
|
467
|
+
**Kinds.** *How* an allow-listed operation is called comes from what the module declares it
|
|
468
|
+
does — `@expose_to_api(kind=...)` or a `"kind"` block in `schema_func.json`, read back as
|
|
469
|
+
`OpKind` (`plesty.lib.device.device_utils`):
|
|
470
|
+
|
|
471
|
+
| kind | the gates |
|
|
472
|
+
|---|---|
|
|
473
|
+
| `read` (default) | called with its declared payload, repeated as it is |
|
|
474
|
+
| `motion` | driven from what `position_key` reports: `target = start ± step` (`relative=True` sends the step itself), then put back; the functions gate fails if it does not come back, the stability gate repeats out-and-back; a motion `slow_op` is a delta from the start and is moved back after the provocation |
|
|
475
|
+
| `acquire` | as `read`, but recorded as acting |
|
|
476
|
+
| `lifecycle` (home, reset) | once, when allow-listed; never repeated |
|
|
477
|
+
| `control` (stop, abort) | once, at rest, after the motions; skipped when nothing moved |
|
|
478
|
+
| `configure` | never called — the round-trip gate covers settings |
|
|
479
|
+
|
|
480
|
+
```python
|
|
481
|
+
@expose_to_api(kind="motion", position_key="MO.Position", step=1.0)
|
|
482
|
+
def move_absolute(self, position: float) -> float: ...
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
`op_kinds={"move_absolute": OpKind("motion", position_key="MO.Position", step=1.0)}` on the
|
|
486
|
+
pipeline overrides the declaration wholesale. A motion without `position_key`, `target` (the
|
|
487
|
+
first required argument by default) or `step` fails the gate rather than guessing how far a part
|
|
488
|
+
may move.
|
|
489
|
+
|
|
467
490
|
**Key parameters:**
|
|
468
491
|
|
|
469
492
|
| Parameter | Default | Description |
|
|
@@ -471,6 +494,7 @@ the function gate is skipped — no motion or emission operation ever runs unatt
|
|
|
471
494
|
| `repetitions` | `20` | Per-command repeat count in the stability gate |
|
|
472
495
|
| `report_path` | `None` | Base path; `run()` writes `<base>.jsonl` (appended) and `<base>.md` |
|
|
473
496
|
| `safe_ops` / `unsafe_ops` | `None` | Operation allow-list and opt-out list |
|
|
497
|
+
| `op_kinds` | `None` | Per-operation `OpKind` overriding the module's declaration |
|
|
474
498
|
| `op_payloads` | `None` | Explicit payload per operation instead of a generated one |
|
|
475
499
|
| `ignore_keys` | `None` | Config keys excluded from every parameter gate |
|
|
476
500
|
| `drain_probe` | auto | The two distinguishable keys used by the drain and recovery gates |
|
|
@@ -84,6 +84,53 @@ Step methods may be sync or async; results are awaited automatically. Each
|
|
|
84
84
|
step is retried (`max_retries`, `retry_sleep` constructor arguments) before
|
|
85
85
|
the run aborts.
|
|
86
86
|
|
|
87
|
+
## Starting on a rig that is really there
|
|
88
|
+
|
|
89
|
+
An experiment's `__main__` builds the rig with `connect_rig` — the same
|
|
90
|
+
start for every experiment: connect the servers, run the composite's
|
|
91
|
+
`preflight()` (each sub-device exposes the methods its configuration
|
|
92
|
+
`requires` and answers `identity()`), and if anything is missing, tell the
|
|
93
|
+
operator which device, at which address, from which variable — and ask:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from plesty.lib.experiment import connect_rig
|
|
97
|
+
|
|
98
|
+
rig, skipped = connect_rig(
|
|
99
|
+
lambda skip: PolPlRig(with_powermeter="pm" not in skip),
|
|
100
|
+
optional={"pm"}, # the experiment can run without it
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
The rig is not ready — 1 device(s) failed the preflight:
|
|
106
|
+
- pm: no answer from tcp://127.0.0.1:5557: TimeoutError('Server did not respond in time')
|
|
107
|
+
address tcp://127.0.0.1:5557 (from PM_ADDRESS in .env / the environment)
|
|
108
|
+
Check that the pm server(s) are running and that PM_ADDRESS point at them
|
|
109
|
+
(a wrong port connects fine but is not the expected device).
|
|
110
|
+
[r] retry (after fixing .env or starting the server) [a] abort [s] continue without pm
|
|
111
|
+
>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`r` rebuilds the rig (an edited `.env` or a freshly started server is picked
|
|
115
|
+
up), `a` raises `RigNotReadyError`, `s` — offered only when every failing
|
|
116
|
+
sub-device is in `optional` — returns the rig without them and names them in
|
|
117
|
+
`skipped`, so the experiment can drop the matching part of its plan. Without
|
|
118
|
+
a terminal on stdin nothing waits: the diagnosis is logged and
|
|
119
|
+
`RigNotReadyError` raised.
|
|
120
|
+
|
|
121
|
+
The check also runs inside `Experiment.setup()`: a run never starts on a
|
|
122
|
+
composite whose `preflight()` reports a problem, whether or not `connect_rig`
|
|
123
|
+
was used. Declare what each sub-device must be able to do in its
|
|
124
|
+
configuration — that is what turns "the port answers" into "it is the
|
|
125
|
+
stage":
|
|
126
|
+
|
|
127
|
+
```yaml
|
|
128
|
+
devices:
|
|
129
|
+
hwp: {timeout_ms: 5000, requires: [home_stage, move_absolute, get_position]}
|
|
130
|
+
spec: {timeout_ms: 5000, requires: [acquire, get_recent_file, write, query]}
|
|
131
|
+
pm: {timeout_ms: 5000, requires: [measure_power, write]}
|
|
132
|
+
```
|
|
133
|
+
|
|
87
134
|
## Data on a shared disk
|
|
88
135
|
|
|
89
136
|
Blobs a device writes itself (spectrometer frames, camera images) never
|
|
@@ -19,19 +19,19 @@
|
|
|
19
19
|
"version": "8.30.1"
|
|
20
20
|
},
|
|
21
21
|
"type": "secret_detection",
|
|
22
|
-
"start_time": "2026-08-
|
|
23
|
-
"end_time": "2026-08-
|
|
22
|
+
"start_time": "2026-08-18T13:39:46",
|
|
23
|
+
"end_time": "2026-08-18T13:39:47",
|
|
24
24
|
"status": "success",
|
|
25
25
|
"observability": {
|
|
26
26
|
"events": [
|
|
27
27
|
{
|
|
28
28
|
"event": "collect_secrets_analyzer_scan_metrics_from_pipeline",
|
|
29
|
-
"time_s": 0.
|
|
29
|
+
"time_s": 0.527730295,
|
|
30
30
|
"exit_code": 0,
|
|
31
31
|
"git_strategy": "FetchShallow",
|
|
32
|
-
"repo_size_kb":
|
|
32
|
+
"repo_size_kb": 709,
|
|
33
33
|
"commit_count": 1,
|
|
34
|
-
"bytes_scanned":
|
|
34
|
+
"bytes_scanned": 317,
|
|
35
35
|
"pipeline_type": "Tag"
|
|
36
36
|
},
|
|
37
37
|
{
|