plesty-lib 0.3.5.dev3__tar.gz → 0.4.0.dev1__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.dev3 → plesty_lib-0.4.0.dev1}/CHANGELOG.md +386 -4
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/PKG-INFO +1 -1
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/analyzer.md +27 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/architecture.md +16 -12
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/base_device.md +46 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/param_system.md +80 -2
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/test_helper.md +67 -3
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/traffic_manager.md +6 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/index.md +8 -5
- plesty_lib-0.4.0.dev1/docs/service.md +112 -0
- plesty_lib-0.4.0.dev1/docs/sim.md +94 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/toc.yaml +2 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/ui.md +95 -3
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/gl-secret-detection-report.json +7 -7
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/composite_device.py +11 -3
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/device_utils.py +52 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/doc.py +22 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/funcs.py +108 -1
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/params.py +234 -3
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/service/__init__.py +44 -1
- plesty_lib-0.4.0.dev1/plesty/lib/service/tcp_ip_server.py +662 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/client_field_test.py +104 -9
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/field_test.py +199 -13
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/__init__.py +9 -2
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/serial.py +28 -3
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/visa.py +417 -27
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/__init__.py +24 -5
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/assets/shell.qss +34 -0
- plesty_lib-0.4.0.dev1/plesty/lib/ui/device_link.py +307 -0
- plesty_lib-0.4.0.dev1/plesty/lib/ui/device_panel.py +552 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/panel.py +15 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/qt/__init__.py +2 -0
- plesty_lib-0.4.0.dev1/plesty/lib/ui/qt/form.py +409 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/qt/shell.py +1 -2
- plesty_lib-0.4.0.dev1/tests/manual/stop_closes_the_device.py +116 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_client_field_test.py +63 -1
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_composite_config.py +50 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_funcs.py +106 -0
- plesty_lib-0.4.0.dev1/tests/test_device_panel.py +461 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_params.py +215 -1
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_field_test.py +78 -4
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_misc.py +12 -2
- plesty_lib-0.4.0.dev1/tests/test_service_loop_factory.py +78 -0
- plesty_lib-0.4.0.dev1/tests/test_tcp_ip_concurrent_serve.py +110 -0
- plesty_lib-0.4.0.dev1/tests/test_tcp_ip_orphan_reply.py +107 -0
- plesty_lib-0.4.0.dev1/tests/test_tcp_ip_stop_signal.py +85 -0
- plesty_lib-0.4.0.dev1/tests/test_tcp_ip_traffic.py +196 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_traffic_drivers.py +79 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_visa_discovery.py +308 -0
- plesty_lib-0.4.0.dev1/tests/test_visa_mav_gating.py +139 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/uv.lock +52 -15
- plesty_lib-0.3.5.dev3/plesty/lib/service/tcp_ip_server.py +0 -363
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/.gitignore +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/.gitlab-ci.yml +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/LICENSE +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/LICENSES/LGPL-3.0-or-later.txt +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/README.md +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/REUSE.toml +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/api_reference.md +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/contributing.md +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/data_schemas.md +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/cmd_solver.md +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/composite_device.md +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/error_handling.md +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/func_system.md +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/index.md +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/logging_system.md +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/experiment.md +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/imgs/device_layers.svg +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/imgs/device_standard.svg +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/imgs/plesty_framework.svg +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/monitor.md +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/quickstart.md +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/__init__.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/analyzer/__init__.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/analyzer/base_analyzer.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/data/__init__.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/data/array.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/data/ctype_manager.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/data/io.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/data/table.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/data/types.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/data/units.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/__init__.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/async_wrapper.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/base_apt_device.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/base_device_sync.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/base_tcp_scpi_device.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/base_visa_scpi_device.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/telemetry.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/__init__.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/assets/preflight.yaml +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/base_experiment.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/journal.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/preflight.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/runs.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/schedule.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/monitor/__init__.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/monitor/assets/viz.yaml +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/monitor/base_monitor.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/monitor/sources.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/monitor/viz.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/service/resource_manager.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/service/tcp_ip_client.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/sim/__init__.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/sim/apt.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/sim/data_generator.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/sim/demo_device.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/solver/__init__.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/solver/iceblock.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/solver/scpi.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/__init__.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/analyzer_pipeline.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/device_func_system.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/device_param_system.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/device_pipeline.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/experiment_pipeline.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/field_test_concurrency.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/grouped_param_keys.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/report_artifact.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/resource_allocation.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/schema_params.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/schema_refresh.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/apt.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/apt_protocol.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/serial_ports.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/tcp_ip.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/usb_utils.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/utils.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/assets/palette.json +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/monitor_panel.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/qt/app.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/qt/plot.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/qt/recorder.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/theme.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/__init__.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/config.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/dll_utils.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/error_utils.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/logger.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/registry.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/settings.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/pyproject.toml +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_analyzer.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_analyzer_pipeline.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_async_wrapper_threading.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_config.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_data_array.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_data_io_hdf5.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_data_types.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_demo_device.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_apt.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_base.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_data_path.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_pipeline.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_scpi.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_telemetry.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_experiment.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_experiment_pipeline.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_experiment_preflight.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_experiment_runs.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_monitor.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_report_artifact.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_schema_params.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_schema_refresh.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_service_manager.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_settings.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_shipped_imports.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_sim.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_solver.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_tcp_ip_resources.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_traffic_orphan.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_traffic_utils.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_ui.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_utils.py +0 -0
- {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_viz.py +0 -0
|
@@ -1,11 +1,392 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- **`DevicePanel` builds a device's controls from its schema (#41).** `Panel`'s
|
|
6
|
+
docstring has named three implementations since it was written; only the live
|
|
7
|
+
view existed, so every module wanting controls in a window hand-wrote a panel
|
|
8
|
+
from `plesty.widgets` primitives — six of them, each restating what its
|
|
9
|
+
device already declares. The duplicate drifts, and it drifts in the direction
|
|
10
|
+
that matters: the hand-written panel for one spectrometer bounded its
|
|
11
|
+
exposure at 0.01–60000 ms and its hardware averaging at 1–1000, where the
|
|
12
|
+
device declares no upper bound on either. Limits that exist nowhere in the
|
|
13
|
+
instrument, invented by whoever wrote the GUI, silently constraining an
|
|
14
|
+
operator.
|
|
15
|
+
|
|
16
|
+
`controls_for()` derives them instead — `dtype` picks the control,
|
|
17
|
+
`min_value`/`max_value` bound it, `options` makes it a drop-down, `read_only`
|
|
18
|
+
a readout, `unit` the suffix, `description` the tooltip — and where the
|
|
19
|
+
device declares no bound the control has none. It is free of any toolkit and
|
|
20
|
+
tested without a display, so a drift fails a test rather than reaching a
|
|
21
|
+
bench. Operations become buttons, with a small form when they take arguments;
|
|
22
|
+
`lifecycle` ones (homing, reset, a dark reference) are left off unless named,
|
|
23
|
+
because they should not be one click away.
|
|
24
|
+
|
|
25
|
+
A panel starts **read-only**. A monitor holds no device and this one
|
|
26
|
+
necessarily does, so it may be pointed at an instrument an experiment is
|
|
27
|
+
driving; writes and operations wait behind *Take control*.
|
|
28
|
+
|
|
29
|
+
- **`describe` carries the device's documentation, not just its method names.**
|
|
30
|
+
It answered `{"methods": [...]}`, and `doc_model()` — though exposed to the
|
|
31
|
+
API — returns a dataclass holding `dtype: type` and a `ResponseParser`, which
|
|
32
|
+
the server refuses to serialize. So nothing across the wire could learn a
|
|
33
|
+
parameter's unit, bounds, options or description: a console or a remote
|
|
34
|
+
client had no way to render a control except by inventing its constraints,
|
|
35
|
+
which is the bug above with a network in the middle. `DeviceDocModel`,
|
|
36
|
+
`ConfigParameter`, `FuncDoc`, `FuncParam` and `FuncOutput` now have
|
|
37
|
+
`to_dict()`, and the reply carries it as `doc` **beside** the existing
|
|
38
|
+
`methods` key — a client written against the old shape is unaffected, and a
|
|
39
|
+
server older than the payload degrades to method names, which `DevicePanel`
|
|
40
|
+
reports rather than showing an empty form. A device whose model fails to
|
|
41
|
+
build still answers `describe`; it is what a supervisor uses to decide the
|
|
42
|
+
device is alive.
|
|
43
|
+
|
|
44
|
+
- **A panel can state its own tone.** `Panel.tone()` returns the state colour of
|
|
45
|
+
the title-bar dot; the shell hardcoded `running` whenever a panel had a status
|
|
46
|
+
line at all, so a panel that fails *while still running* — a device that
|
|
47
|
+
stopped answering — had no way to go red.
|
|
48
|
+
|
|
49
|
+
- **A long call holds the instrument, not the socket (#48).** The accept loop
|
|
50
|
+
answered one request at a time, inline, so nothing was even read while a
|
|
51
|
+
device call ran. A supervisor asks `describe` on a timer — the console does
|
|
52
|
+
it per device every three seconds — and every probe arriving during a 20 s
|
|
53
|
+
exposure queued behind it, timed out, reconnected, and had its reply dropped
|
|
54
|
+
as unroutable: a bench read as unreachable for the whole of every long
|
|
55
|
+
acquisition, and the log carried a warning per probe for work nobody had
|
|
56
|
+
lost. Requests are now answered in their own task, so the loop keeps
|
|
57
|
+
receiving. Device work stays serialized exactly as before — the async
|
|
58
|
+
wrapper holds a per-device lock around every call — and what overtakes it is
|
|
59
|
+
`describe`, which is introspection over the class and the op registry and
|
|
60
|
+
touches no hardware. Replies are serialized on their own lock, because a
|
|
61
|
+
reply is two frames on one shared ROUTER socket.
|
|
62
|
+
|
|
63
|
+
A lost *liveness* reply is also no longer a warning. `disconnect` was
|
|
64
|
+
already excluded for the same reason: the warning exists for an answer to
|
|
65
|
+
work the device has already done, and a probe that reads state and moves no
|
|
66
|
+
instrument loses nothing when its reply misses.
|
|
67
|
+
|
|
68
|
+
- **`CompositeDevice.identity()` runs on the sub-device's own thread (#49).**
|
|
69
|
+
It was the one path that called the client directly instead of going through
|
|
70
|
+
`submit` — `call`, `preflight`, `set_data_path` and `disconnect_all` all hop
|
|
71
|
+
first. Under `thread_affinity` a sub-device's ZMQ socket belongs to its
|
|
72
|
+
thread, so asking for the identity from another one put two threads on that
|
|
73
|
+
socket and the replies crossed: a magneto-PL rig whose power regulator was
|
|
74
|
+
sampling the meter got the identity string back from `measure_power`. The
|
|
75
|
+
crash was the lucky case — two calls of the same shape crossing swap two
|
|
76
|
+
readings and say nothing.
|
|
77
|
+
|
|
78
|
+
- **The functions gate now issues a control operation into a move, instead of
|
|
79
|
+
at a stage already parked.** #39 built the `control` kind but drove it at
|
|
80
|
+
rest: controls were sorted last and the stop went out after every motion had
|
|
81
|
+
finished and been put back. That proves the command is accepted, not that it
|
|
82
|
+
stops anything — and the case that matters is the other one. k10cr1#2, motion
|
|
83
|
+
methods returning before the stage had settled, was found by hand at a bench;
|
|
84
|
+
an in-flight stop is the gate that would have caught it.
|
|
85
|
+
|
|
86
|
+
The move is put in flight the way the drain gate provokes a slow call: the
|
|
87
|
+
transport's timeout is shortened so the command goes out and its answer is
|
|
88
|
+
abandoned, leaving the part travelling. Then the control is issued, and three
|
|
89
|
+
things have to hold — it returns while the move is still in flight,
|
|
90
|
+
`position_key` reads short of where the move was heading, and the part answers
|
|
91
|
+
afterwards and goes back where it started. The same shape runs over the wire
|
|
92
|
+
in `ClientFieldTest`, which takes `slow_op_payload` for it.
|
|
93
|
+
|
|
94
|
+
No worker thread drives the device. Servers pin device calls to one thread
|
|
95
|
+
because drivers turned out to have thread *affinity*, and a gate that called
|
|
96
|
+
one device from two threads would risk failing for a reason that has nothing
|
|
97
|
+
to do with the control operation.
|
|
98
|
+
|
|
99
|
+
**A module that stops late now fails a gate that passed before**, and a
|
|
100
|
+
control is skipped — with the reason — where no motion is allow-listed or no
|
|
101
|
+
`slow_op_payload` delta is declared, rather than being called at rest to no
|
|
102
|
+
purpose. The gate's evidence is also bound before the loop rather than after
|
|
103
|
+
it, so a run that fails still reports what it did, including whether a
|
|
104
|
+
stopped part was put back.
|
|
105
|
+
|
|
106
|
+
- **A client holding an allocation can call schema operations again, and those
|
|
107
|
+
calls are now access-controlled.** The server appends the caller's allocation
|
|
108
|
+
to every `call` it forwards, but a schema operation is a generated closure
|
|
109
|
+
that validates its inputs strictly and so rejected the injected key by name:
|
|
110
|
+
`KeyError: measure_analog() got unexpected parameter(s): ['resources']`. Any
|
|
111
|
+
device that registers resources and exposes schema operations failed on
|
|
112
|
+
*every* operation call from an allocated client, which made per-client
|
|
113
|
+
locking unusable on schema-driven devices — that is, on all of them.
|
|
114
|
+
`@expose_to_api` methods escaped it only because a `**kwargs` absorbed the
|
|
115
|
+
key; synthetic operations have no signature to widen, and the strictness is
|
|
116
|
+
what enforces the schema's types and ranges.
|
|
117
|
+
|
|
118
|
+
`resources` is now taken out before validation and carried in the solver
|
|
119
|
+
request beside `func_meta`, where a solver can see the allocation alongside
|
|
120
|
+
the channels the arguments name.
|
|
121
|
+
|
|
122
|
+
The same gap left operations unguarded: `_user_permission_check` was
|
|
123
|
+
consulted in `write` and `query` only, so once the key stopped raising, a
|
|
124
|
+
client holding `ao0` could still run any operation on any line. Operations
|
|
125
|
+
now consult it too, before validating their arguments — a caller who may not
|
|
126
|
+
run one learns nothing about its inputs. A denied operation raises
|
|
127
|
+
`PermissionError` rather than returning `False` as `write` does, because an
|
|
128
|
+
operation's return value is arbitrary and a `False` denial is
|
|
129
|
+
indistinguishable from a legitimate result. A `FunctionSystem` used on its
|
|
130
|
+
own has no such policy and permits everything, as before.
|
|
131
|
+
|
|
132
|
+
- **`run_server()` picks the server's event loop explicitly, so a global
|
|
133
|
+
policy no longer has to.** Importing `plesty.lib.service` installs a Windows
|
|
134
|
+
loop policy as a side effect: that is what lets a bare `asyncio.run` in a
|
|
135
|
+
generated `__main__` get a selector loop, which pyzmq needs because
|
|
136
|
+
`ProactorEventLoop` has no `add_reader`. Both `asyncio.set_event_loop_policy`
|
|
137
|
+
and `WindowsSelectorEventLoopPolicy` are deprecated in Python 3.14 — two
|
|
138
|
+
warnings on every device server import on a bench — and removed in 3.16,
|
|
139
|
+
where the import would raise instead.
|
|
140
|
+
|
|
141
|
+
`plesty.lib.service.run_server(main())` replaces `asyncio.run(main())` and
|
|
142
|
+
hands the loop factory to the call site, which is the supported mechanism
|
|
143
|
+
and works on every Python this package targets. `server_loop_factory()`
|
|
144
|
+
exposes the same choice for callers that build their own runner.
|
|
145
|
+
`SelectorEventLoop` itself is not deprecated; only the policy machinery is.
|
|
146
|
+
|
|
147
|
+
The policy install stays for now, skipped only where it would raise rather
|
|
148
|
+
than warn (3.16+), because it is the one thing keeping an unmigrated entry
|
|
149
|
+
point working on Windows. Nothing on a bench changes behaviour: this adds
|
|
150
|
+
the supported path and leaves the old one standing until the entry points
|
|
151
|
+
move (sdk#35).
|
|
152
|
+
|
|
153
|
+
- **The Analyzer contract is locked, and now says so.** It shipped in 0.3.2
|
|
154
|
+
and has not changed since, the SDK's gate a1 and `plesty init analyzer`
|
|
155
|
+
generate against it, and the pilot migration put a real analysis module on
|
|
156
|
+
it without touching any analysis code. All of that was true and none of it
|
|
157
|
+
was written down where an analyzer author would look: `docs/analyzer.md`
|
|
158
|
+
described the interface without saying whether it was safe to depend on,
|
|
159
|
+
which is the one question a contributor has before writing the first
|
|
160
|
+
subclass. A new "Contract stability" section states what is fixed — the two
|
|
161
|
+
schemas, the single `analyze()`, the validating `__call__`, the provenance
|
|
162
|
+
stamp, the five pipeline gates — what may still be added, and what would
|
|
163
|
+
take a major version to change. Documentation only; no code changed.
|
|
164
|
+
|
|
165
|
+
- **Every served request now writes one record to the log**, so a monitor can
|
|
166
|
+
show what is on the wire without a new transport or a wire-format change.
|
|
167
|
+
The record carries what was asked, of what, by whom, how it ended and how
|
|
168
|
+
long it took — and never the payload: a result can be a spectrum or a stack
|
|
169
|
+
of frames, and a log is the wrong home for measurement data. A failure
|
|
170
|
+
records the exception *type*, not its message, which can carry a path or an
|
|
171
|
+
address. A reply that could not be routed is recorded as `delivered: false`
|
|
172
|
+
with its outcome intact — the client timed out and reconnected, the device
|
|
173
|
+
did the work anyway, and the caller is about to report a failure that did
|
|
174
|
+
not happen; that case is the one a monitor most needs to show.
|
|
175
|
+
|
|
176
|
+
A `describe` is recorded at `DEBUG` rather than `INFO` — liveness, not work.
|
|
177
|
+
A supervisor watching a bench polls it on a timer (plesty-bench's console
|
|
178
|
+
asks every declared device every three seconds, whether or not anyone is
|
|
179
|
+
looking) and each probe opens a fresh socket, so it arrives under a new
|
|
180
|
+
client identity every time. At `INFO` that is some 1,200 records per device
|
|
181
|
+
per hour on an idle bench, and a monitor would show almost nothing but the
|
|
182
|
+
console asking whether the server is alive, with the calls that moved the
|
|
183
|
+
instrument buried among them. Recorded, not dropped: raising the logger to
|
|
184
|
+
`DEBUG` brings them back.
|
|
185
|
+
|
|
186
|
+
Written through the `plesty.server.traffic` logger, prefixed `[TRAFFIC]`, so
|
|
187
|
+
it can be quieted or raised on its own:
|
|
188
|
+
`logging.getLogger("plesty.server.traffic").setLevel(logging.WARNING)`. One
|
|
189
|
+
line per request — a stage polled at 5 Hz writes roughly 2 MB an hour, and
|
|
190
|
+
the history reaches back exactly as far as the log does. Reporting never
|
|
191
|
+
raises: a malformed request or a throwing handler costs the record, not the
|
|
192
|
+
request. For plesty-bench#3.
|
|
193
|
+
|
|
194
|
+
- **A Windows stop now closes the device instead of killing the server.**
|
|
195
|
+
`CTRL_BREAK_EVENT` is the only console control event a supervisor can aim
|
|
196
|
+
at one process group, so it is what stops a single device server; Python
|
|
197
|
+
delivers it as `SIGBREAK`, whose default action is to terminate the process
|
|
198
|
+
outright (exit `0xC000013A`). Nothing installed a handler — not this
|
|
199
|
+
library, not the SDK scaffold — so every generated server was killed on
|
|
200
|
+
every stop: no traceback, no output, and the `finally` that closes the
|
|
201
|
+
device never ran. Whatever `__aexit__` releases was leaked each time. For a
|
|
202
|
+
spectrometer that was an orphaned `AddInProcess` still holding the camera,
|
|
203
|
+
which then refused the next start until an elevated `taskkill` or a power
|
|
204
|
+
cycle cleared it.
|
|
205
|
+
|
|
206
|
+
`DeviceTCPIPServer.run` now serves inside `stop_on_break()`, which hands
|
|
207
|
+
`SIGBREAK` to the interpreter's own `SIGINT` handler for the duration and
|
|
208
|
+
restores what it found afterwards. A stop raises `KeyboardInterrupt` in the
|
|
209
|
+
main thread on every platform, so the existing shutdown path runs and the
|
|
210
|
+
device is released. Servers get this by upgrading the library — no
|
|
211
|
+
regeneration from the template. Off Windows, and off the main thread where
|
|
212
|
+
handlers cannot be installed at all, it does nothing.
|
|
213
|
+
|
|
214
|
+
- **A missed disconnect acknowledgement and a lost answer no longer read as
|
|
215
|
+
the same event.** The ROUTER refuses to route a reply whose client has
|
|
216
|
+
gone, and one warning covered both. A `disconnect` ack that misses the
|
|
217
|
+
composite's one-second window is routine — the client asked to be released
|
|
218
|
+
and the server was a moment slow finishing an acquisition — and it happens
|
|
219
|
+
once per teardown, which is what made the log unreadable. It is now debug.
|
|
220
|
+
The other case is an answer to work the device has already done: the caller
|
|
221
|
+
saw a timeout and will report a failure that did not happen. That warns and
|
|
222
|
+
names what was asked for (`call acquire`, `write CameraSettings…`), because
|
|
223
|
+
a reply reported as merely lost is not something anyone can act on
|
|
224
|
+
afterwards; a request that cannot be parsed is assumed to matter rather
|
|
225
|
+
than dismissed.
|
|
226
|
+
|
|
227
|
+
- **`VI_ERROR_IN_PROGRESS` at open is no longer reported as a wedged
|
|
228
|
+
instrument.** The previous entry called it a finished diagnosis — wedged
|
|
229
|
+
before the session existed, every command will time out, only a power cycle
|
|
230
|
+
helps — and said so at error level. The logs do not support it. On one PM101R
|
|
231
|
+
it was the opening state of **ten of twelve** consecutive starts, and connect
|
|
232
|
+
succeeded on all twelve: identity, sensor query and limit reads all answered
|
|
233
|
+
normally. The message was telling people to unplug a working instrument,
|
|
234
|
+
which is the fastest way to spend the credibility the real remedy needs.
|
|
235
|
+
|
|
236
|
+
It is now recorded at debug and interpreted later. The diagnosis needs two
|
|
237
|
+
facts, and neither means anything alone: the clear-on-open found a transfer
|
|
238
|
+
in flight, **and** nothing on the session has ever been answered. One answer
|
|
239
|
+
proves the instrument was reachable, so a later failure is an ordinary fault
|
|
240
|
+
for the recovery ladder; a session that opened onto someone else's transfer
|
|
241
|
+
and has never been answered is the case worth a paragraph. Found by reading
|
|
242
|
+
the whole log rather than the starts we happened to trigger — a four-sample
|
|
243
|
+
read of the same data had suggested an alternating TMO/IN_PROGRESS pattern
|
|
244
|
+
that twelve samples show is simply IN_PROGRESS 10 of 12.
|
|
245
|
+
|
|
246
|
+
- **A query now waits for MAV before it reads, so a silent instrument strands
|
|
247
|
+
nothing.** This is the cause behind the entry below rather than another
|
|
248
|
+
symptom of it. A USBTMC query is two exchanges — the command out, then
|
|
249
|
+
`REQUEST_DEV_DEP_MSG_IN` and a bulk-in read — and an instrument is entitled
|
|
250
|
+
to stay silent: an undefined header is answered with `-113` in the error
|
|
251
|
+
queue and no response at all. When that happened the read timed out and we
|
|
252
|
+
walked away while the device still held the request. Nothing retracted it.
|
|
253
|
+
`viClear` cannot be queued behind a pending bulk-in, which is exactly the
|
|
254
|
+
`VI_ERROR_IN_PROGRESS` the ladder kept reporting, and reopening the session
|
|
255
|
+
replaces a host-side handle without touching the device — so the ladder's own
|
|
256
|
+
diagnosis was right and every rung it had was powerless against it. Five such
|
|
257
|
+
queries in one session stopped a PM101R and a PM400 servicing USB-TMC at all,
|
|
258
|
+
on different benches, and only unplugging them helped.
|
|
259
|
+
|
|
260
|
+
`_query_when_ready` reads the status byte first and issues the read only once
|
|
261
|
+
bit 4 says a message is waiting. Measured on the PM101R this bug had already
|
|
262
|
+
cost a power cycle: `MEAS:ENER?`, one of the five that wedged it, became a
|
|
263
|
+
four-second non-event — the status byte sat at `0x04` (EAV set, MAV clear: an
|
|
264
|
+
error queued, no answer coming) across forty polls, and the next three
|
|
265
|
+
queries answered in single-digit milliseconds. Reading the status byte costs
|
|
266
|
+
a control transfer and touches no bulk endpoint, which is what makes it safe
|
|
267
|
+
to ask before committing to a read.
|
|
268
|
+
|
|
269
|
+
Silence now raises `NoMessageError`, a `VisaIOError` carrying the timeout
|
|
270
|
+
code so existing callers are unchanged, and the ladder skips every rung for
|
|
271
|
+
it: there is no transfer to rescue, and a clear cannot conjure an answer out
|
|
272
|
+
of an instrument that has already declined to give one. Three timeouts and a
|
|
273
|
+
session teardown become one clean "it does not answer that".
|
|
274
|
+
|
|
275
|
+
Two guards, because a status byte that never sets MAV is ambiguous — the
|
|
276
|
+
instrument may have nothing to say, or may simply not maintain the bit. The
|
|
277
|
+
gate does not fire until MAV has been seen once on the session; before that a
|
|
278
|
+
silent byte falls back to reading anyway, which is precisely the old
|
|
279
|
+
behaviour and no worse, and an answer switches gating off with a warning. A
|
|
280
|
+
backend that cannot report a status byte at all falls back the same way.
|
|
281
|
+
|
|
282
|
+
**This is prevention, not recovery, and the difference matters.** Nothing
|
|
283
|
+
here implements the USBTMC abort sequence (`ABORT_BULK_IN` →
|
|
284
|
+
`CHECK_ABORT_BULK_IN_STATUS` → `INITIATE_CLEAR`), so a transfer that does get
|
|
285
|
+
stranded still needs the instrument to lose power. Three routes remain: the
|
|
286
|
+
first query of a session, which is unguarded until MAV has been proven; a
|
|
287
|
+
resource without a separable status read, which keeps the old path; and
|
|
288
|
+
`stb_gated_queries` switched off. The first is the one that matters — a
|
|
289
|
+
session whose *opening* query is speculative, which is exactly the
|
|
290
|
+
uncharacterised-head probe this fix exists for. Prove MAV with something the
|
|
291
|
+
instrument certainly answers before asking it anything it may not.
|
|
292
|
+
|
|
293
|
+
- **An instrument that arrived wedged now says so, on the first line.** The
|
|
294
|
+
clear-on-open is best effort and its failure was logged as such — but one
|
|
295
|
+
failure there is a diagnosis, not a symptom. `VI_ERROR_IN_PROGRESS` on a
|
|
296
|
+
session opened seconds ago cannot describe anything this process did: a
|
|
297
|
+
fresh session has issued no operation to still be in progress, so the
|
|
298
|
+
*instrument* was holding a stranded transfer before the session existed. It
|
|
299
|
+
is now reported at error level, saying what it means and what actually
|
|
300
|
+
clears it — because the two things anyone tries first both fail. Restarting
|
|
301
|
+
the server rebuilds a session against the same stuck endpoint, and a
|
|
302
|
+
driver-level re-enumeration (`Disable-PnpDevice`/`Enable-PnpDevice`,
|
|
303
|
+
`pnputil /restart-device`) resets the port without ever dropping bus power,
|
|
304
|
+
so a transfer held in the instrument's firmware rides straight through it.
|
|
305
|
+
Only power loss clears it, which for a bus-powered USB instrument means
|
|
306
|
+
unplugging the cable.
|
|
307
|
+
|
|
308
|
+
This came out of a PM101R that was diagnosed from thirty lines of identical
|
|
309
|
+
timeouts while the answer sat above them as a generic warning. Every other
|
|
310
|
+
clear failure — the timeout that is the documented benign case, an
|
|
311
|
+
instrument with no clear at all — stays the quiet warning it was, so the
|
|
312
|
+
new message means exactly one thing when it appears. The clear-on-open also
|
|
313
|
+
discards the host-side buffers first, as the recovery ladder's clear rung
|
|
314
|
+
already does.
|
|
315
|
+
|
|
316
|
+
- **The VISA recovery ladder no longer compounds a wedge.** A PM101R with no
|
|
317
|
+
sensor head answered `*IDN?` and took writes, but every sensor-dependent
|
|
318
|
+
query timed out — and the ladder then made it worse until the meter answered
|
|
319
|
+
nothing at all. Four faults, each fixed:
|
|
320
|
+
- A timeout can leave the session with a transfer still in flight. Every
|
|
321
|
+
operation on such a session reports `VI_ERROR_IN_PROGRESS`, the device
|
|
322
|
+
clear included, so the clear-and-retry rung could never succeed and only
|
|
323
|
+
spent a second timeout to say so. That error now goes straight to the
|
|
324
|
+
reopen, which is the only rung that clears the condition.
|
|
325
|
+
- A wedged session refuses to close, and the refusal was logged and the
|
|
326
|
+
reference dropped — which frees nothing. The backend kept the handle and
|
|
327
|
+
the instrument kept the interface, so each reopen added *another* live
|
|
328
|
+
session to the same device; two rungs left the meter holding three, at
|
|
329
|
+
which point `*IDN?` stopped answering on all of them. A refused close now
|
|
330
|
+
falls back to closing the underlying VISA handle, which does not go
|
|
331
|
+
through the session and so cannot be blocked by its state.
|
|
332
|
+
- The reopen called `open_resource` directly, bypassing the watchdog that
|
|
333
|
+
`_open` uses — on the one path that runs precisely when the instrument is
|
|
334
|
+
not answering, and where opening can therefore hang in native code. It now
|
|
335
|
+
opens through the same watchdog.
|
|
336
|
+
- A reopen that failed left the address registered to a session that no
|
|
337
|
+
longer existed, so `is_open` was false, every later command was refused,
|
|
338
|
+
and `open()` rejected every retry as "already in use" for the life of the
|
|
339
|
+
process. The address is now unclaimed before the release is attempted and
|
|
340
|
+
re-registered on success, so a failed reopen leaves the manager closed but
|
|
341
|
+
reopenable rather than needing a server restart.
|
|
342
|
+
|
|
343
|
+
The clear rung also discards the host-side buffers first: a timed-out
|
|
344
|
+
query's answer may still be half-arrived, and the clear reaches the
|
|
345
|
+
instrument but not the buffers, so the remnant became the reply to the next
|
|
346
|
+
command. `_close` releases through the same path, so a device server shutting
|
|
347
|
+
down on a wedged instrument no longer leaves the interface claimed.
|
|
348
|
+
|
|
349
|
+
None of this makes an absent sensor head measure anything — the underlying
|
|
350
|
+
query still fails. It stops one hardware fault from taking the session, and
|
|
351
|
+
then the process, down with it.
|
|
352
|
+
|
|
353
|
+
## 0.3.5 — 2026-08-24
|
|
354
|
+
|
|
355
|
+
The final release of the line begun in 0.3.5.dev1: everything in the three
|
|
356
|
+
pre-releases below ships here — the run convention (#34), bounded visible
|
|
357
|
+
connects, the rig preflight and `connect_rig` (#36, #37), default logging,
|
|
358
|
+
operation kinds driving the field test at both tiers (#39), and the pol-pl
|
|
359
|
+
rig findings (#35, #38) — plus the parser change below.
|
|
360
|
+
|
|
361
|
+
Note for consumers upgrading from 0.3.4: the run sources' `pattern=`
|
|
362
|
+
arguments and `data_dir` attributes are gone and `Experiment(run_root=)`
|
|
363
|
+
now defaults to the mount. Adjust those call sites.
|
|
364
|
+
|
|
365
|
+
- **A schema can name its response parser** (#40). `parser` was listed among
|
|
366
|
+
the schema keys but only ever held a `ResponseParser` object, so a name
|
|
367
|
+
written there was stored unchanged and *called* on the first reply. It is
|
|
368
|
+
now resolved to a parser when the parameter is registered — a misspelled
|
|
369
|
+
kind, an invalid pattern or an out-of-range capture group fails while the
|
|
370
|
+
schema loads, naming the parameter. Two built-in kinds cover the text
|
|
371
|
+
shapes every instrument has: `regex` (`pattern`, `group`) pulls the value
|
|
372
|
+
out of a reply that carries text around it, `delimited` (`index`,
|
|
373
|
+
`separator`) keeps one field of a record; both hand the result to
|
|
374
|
+
`AutoResponseParser`, so the parameter's `type` still does the conversion,
|
|
375
|
+
and both decode a `bytes` reply first. `auto` stays the default and
|
|
376
|
+
unchanged — a reply it cannot cast is still returned raw. A device whose
|
|
377
|
+
frame the built-ins cannot describe registers its own decoder once with
|
|
378
|
+
`register_parser_kind(name, factory)` and names it per parameter from the
|
|
379
|
+
schema; `parser_kinds()` lists what is nameable. Passing a
|
|
380
|
+
`ResponseParser` instance from Python is untouched. This retires the
|
|
381
|
+
private `pattern` key `plesty-spectrapro-hrs` had invented for exactly
|
|
382
|
+
this.
|
|
383
|
+
|
|
3
384
|
## 0.3.5.dev3 — 2026-08-18
|
|
4
385
|
|
|
5
386
|
Development pre-release for field-testing operation kinds (#39): operations
|
|
6
387
|
declare what they do and the field test drives a motion out and back, a
|
|
7
388
|
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.
|
|
389
|
+
and #38. Same line as 0.3.5.dev2 — the final release is 0.3.5.
|
|
9
390
|
|
|
10
391
|
- **Operations declare what they do, and the field test drives them by
|
|
11
392
|
kind** (#39). `@expose_to_api(kind=…)` and a schema `kind` block record
|
|
@@ -72,7 +453,7 @@ and #38. Same line as 0.3.5.dev2 — the final release is 0.4.0.
|
|
|
72
453
|
|
|
73
454
|
Development pre-release for field-testing the rig start-up (#36, #37):
|
|
74
455
|
bounded, visible connects, the preflight, and default logging. Same line as
|
|
75
|
-
0.3.5.dev1 — the final release is 0.
|
|
456
|
+
0.3.5.dev1 — the final release is 0.3.5.
|
|
76
457
|
|
|
77
458
|
- **A run starts only on a rig that is really there** (pol-pl field
|
|
78
459
|
finding: a sweep went on with the powermeter server absent).
|
|
@@ -132,9 +513,10 @@ bounded, visible connects, the preflight, and default logging. Same line as
|
|
|
132
513
|
## 0.3.5.dev1 — 2026-08-17
|
|
133
514
|
|
|
134
515
|
Development pre-release for field-testing the run convention (#34); the
|
|
135
|
-
final release of this line is 0.
|
|
516
|
+
final release of this line is 0.3.5. The API diff against 0.3.4 — the
|
|
136
517
|
`pattern=` arguments and `data_dir` attributes of the run sources are
|
|
137
|
-
gone, `Experiment(run_root=)` defaults to the mount
|
|
518
|
+
gone, `Experiment(run_root=)` defaults to the mount — needs those call
|
|
519
|
+
sites adjusted.
|
|
138
520
|
|
|
139
521
|
- `plesty.lib.monitor.Viz` (#34): the `monitor` / `render` command line over
|
|
140
522
|
an experiment's panels. The experiment registers one function
|
|
@@ -54,6 +54,33 @@ Schema entries use the same dtype vocabulary as device schemas
|
|
|
54
54
|
`array_<numpy_dtype>`, `table2d`, … Arrays may declare a `shape` where `None`
|
|
55
55
|
marks a free dimension (`[None, 2]`); inputs may declare `required: False`.
|
|
56
56
|
|
|
57
|
+
## Contract stability
|
|
58
|
+
|
|
59
|
+
**This contract is locked.** It shipped in 0.3.2 and has not changed since.
|
|
60
|
+
Write an analyzer against what is described above and it will keep working.
|
|
61
|
+
|
|
62
|
+
Locked means the declared surface is fixed: the two class-level schemas, the
|
|
63
|
+
single `analyze()`, the validating `__call__` and its four steps, the
|
|
64
|
+
provenance stamp, and the five `AnalyzerPipeline` gates below. Names and
|
|
65
|
+
shapes here are not going to be rearranged under you.
|
|
66
|
+
|
|
67
|
+
It is locked on evidence rather than by declaration. The pilot migration
|
|
68
|
+
(`hub/analyzers/positionestimate`) put a real analysis module on the contract
|
|
69
|
+
without changing any of its analysis code — the facade was about 170 lines,
|
|
70
|
+
free-dimension shapes and optional inputs both behaved as specified, and all
|
|
71
|
+
five gates passed. The SDK then built on it: gate a1 and `plesty init
|
|
72
|
+
analyzer` generate and check against exactly this surface. A contract two
|
|
73
|
+
independent consumers already depend on is one that has been tested by use,
|
|
74
|
+
which is the only kind of locking worth stating.
|
|
75
|
+
|
|
76
|
+
What can still change: additions. A new optional schema key, a new dtype in
|
|
77
|
+
the shared vocabulary, a new gate — none of those invalidate an analyzer
|
|
78
|
+
written today. What cannot change without a major version and a deprecation
|
|
79
|
+
period: the four things in the paragraph above. If you find the contract
|
|
80
|
+
genuinely cannot express your transform, that is an issue on plesty-lib, not
|
|
81
|
+
a local workaround — the value of the stamp and the gates is that every
|
|
82
|
+
analyzer carries the same ones.
|
|
83
|
+
|
|
57
84
|
## Design rules
|
|
58
85
|
|
|
59
86
|
- **Synchronous by design.** Analysis is CPU-bound compute, unlike the I/O
|
|
@@ -16,7 +16,7 @@ Everything ships under the `plesty.lib` namespace package:
|
|
|
16
16
|
|---|---|
|
|
17
17
|
| `plesty.lib.utils` | Foundation utilities: logging setup, error handling, the process-wide `ResourceRegistry`, `EnvSettings`, the module/run configuration loader (`module_config`, `load_yaml`), DLL helpers. |
|
|
18
18
|
| `plesty.lib.data` | Data containers and persistence: `PlestyArray`, tables, units, dtype helpers, and the result store (`save_result` / `ResultDocument`, optional HDF5 export). |
|
|
19
|
-
| `plesty.lib.traffic` | Transport backends (traffic managers): Serial, VISA, USB utilities, raw TCP/IP. Moves bytes; knows nothing about parameters or operations. |
|
|
19
|
+
| `plesty.lib.traffic` | Transport backends (traffic managers): Serial, VISA, USB utilities, raw TCP/IP, and Thorlabs APT (binary framing, `apt_protocol`). Moves bytes; knows nothing about parameters or operations. |
|
|
20
20
|
| `plesty.lib.solver` | Protocol translation: the `CmdSolver` / `OpSolver` ABCs and concrete solvers (`SCPISolver`, `ICEBLOCKSolver`) that turn standardized calls into vendor commands. |
|
|
21
21
|
| `plesty.lib.device` | The core device tier: `BaseDeviceSyncModel` with its three system mixins (`ConfigSystem`, `FunctionSystem`, `TelemetrySystem`), the render-agnostic `doc_model()`, async wrappers, ready-made SCPI base devices, and `CompositeDevice`. |
|
|
22
22
|
| `plesty.lib.service` | Remote access: the ZMQ-based device TCP/IP server and client, plus the per-device `ResourceManager` for multi-client access control. |
|
|
@@ -24,8 +24,8 @@ Everything ships under the `plesty.lib` namespace package:
|
|
|
24
24
|
| `plesty.lib.experiment` | Reproducible orchestration: the async `Experiment` ABC with `Plan`/`Step` schedules and the crash-safe `Journal`. |
|
|
25
25
|
| `plesty.lib.monitor` | Live views: the `Monitor` ABC — schema-declared projections of arriving data onto declared traces — and the sources that feed it (`RunSource`, `TelemetrySource`, `PushSource`). Read-only and toolkit-free. |
|
|
26
26
|
| `plesty.lib.ui` | GUI framework: the `Panel` contract, the dockable `Shell` home window, the `Theme` carrying the plesty.net design tokens, and the PySide6/pyqtgraph backend behind the `gui` extra. |
|
|
27
|
-
| `plesty.lib.sim` | `DataGenerator`
|
|
28
|
-
| `plesty.lib.test` | Shipped test helpers and the standard contract pipelines
|
|
27
|
+
| `plesty.lib.sim` | Stand-ins for hardware: `DataGenerator` (schema-driven synthetic data for mock solvers and tests), `DemoDevice` (the standard demo device tests build on instead of hand-writing one), and `AptMotorSimulator` (a Thorlabs APT motor answering real protocol frames). |
|
|
28
|
+
| `plesty.lib.test` | Shipped test helpers and the standard contract pipelines that hub modules import: `DevicePipeline`, `ExperimentPipeline`, `AnalyzerPipeline` (mock, hardware-free), plus `FieldTestPipeline`, `client_field_test` and `field_test_concurrency` for what only an instrument can prove. |
|
|
29
29
|
|
|
30
30
|
### Layering
|
|
31
31
|
|
|
@@ -143,15 +143,19 @@ file is the commit record. `load_document` reads metadata without blob I/O;
|
|
|
143
143
|
`convert_to_hdf5` optionally packs documents into one archive. See
|
|
144
144
|
[Data & Metadata Schemas](data_schemas.md).
|
|
145
145
|
|
|
146
|
-
**Contract test pipelines** — `plesty.lib.test`. `DevicePipeline` (
|
|
147
|
-
gates, SDK gate d1), `ExperimentPipeline` (five hardware-free gates,
|
|
148
|
-
gate E1), and `AnalyzerPipeline` (five gates on schema-generated
|
|
149
|
-
inputs, groundwork for SDK gate a1) define what every hub module
|
|
150
|
-
schema integrity, mock round-trips, lifecycle
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
146
|
+
**Contract test pipelines** — `plesty.lib.test`. `DevicePipeline` (eight
|
|
147
|
+
mock gates, SDK gate d1), `ExperimentPipeline` (five hardware-free gates,
|
|
148
|
+
SDK gate E1), and `AnalyzerPipeline` (five gates on schema-generated
|
|
149
|
+
synthetic inputs, groundwork for SDK gate a1) define what every hub module
|
|
150
|
+
must prove: schema integrity, mock round-trips, lifecycle (device tier);
|
|
151
|
+
deterministic plans, resolvable ops, serializability (experiment tier);
|
|
152
|
+
analyze-signature match, round-trip validation, provenance (analyzer tier).
|
|
153
|
+
A device module exposes one pytest function calling `run_mock_pipeline()`;
|
|
154
|
+
experiment and analyzer modules expose one function per gate. What a mock
|
|
155
|
+
cannot reach — the command-format contract, transport pathologies, resource
|
|
156
|
+
contention — belongs to `FieldTestPipeline` on the instrument,
|
|
157
|
+
`client_field_test` over the wire, and `field_test_concurrency` for devices
|
|
158
|
+
sharing a host. See [Test helpers](device/test_helper.md).
|
|
155
159
|
|
|
156
160
|
## Design Decisions
|
|
157
161
|
|
|
@@ -122,6 +122,52 @@ class MyScpiSerialDevice(BaseDeviceSyncModel):
|
|
|
122
122
|
return self.traffic_manager is not None and self.traffic_manager.is_open
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
+
## Ready-Made Base Devices
|
|
126
|
+
|
|
127
|
+
Before subclassing `BaseDeviceSyncModel` directly, check whether one of the
|
|
128
|
+
shipped intermediates already covers your transport. Each wires up a traffic
|
|
129
|
+
manager and implements `connect`, `disconnect`, `_write_`, `_query_` and
|
|
130
|
+
`check_operatability`, so a module only declares its schemas and its
|
|
131
|
+
device-specific operations.
|
|
132
|
+
|
|
133
|
+
| Class | Location | For |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `BaseVisaScpiDevice` | `plesty.lib.device.base_visa_scpi_device` | Any SCPI instrument reachable over VISA (USB-TMC, GPIB, serial-over-VISA). |
|
|
136
|
+
| `BaseTCPScpiDevice` | `plesty.lib.device.base_tcp_scpi_device` | A SCPI instrument that listens on a raw TCP socket rather than through VISA. |
|
|
137
|
+
| `BaseAptDevice` | `plesty.lib.device.base_apt_device` | Single-channel Thorlabs APT motion controllers — binary frames rather than text. |
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from plesty.lib.device.base_visa_scpi_device import BaseVisaScpiDevice
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class MyPowerMeter(BaseVisaScpiDevice):
|
|
144
|
+
def __init__(self, address: str):
|
|
145
|
+
super().__init__(
|
|
146
|
+
address,
|
|
147
|
+
timeout=5000,
|
|
148
|
+
param_schema="assets/param_schema.json",
|
|
149
|
+
op_schema="assets/op_schema.json",
|
|
150
|
+
)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Both SCPI bases take `write_termination` / `read_termination` (and an optional
|
|
154
|
+
`baud_rate` for serial-backed resources), pair themselves with
|
|
155
|
+
[`SCPISolver`](cmd_solver.md), and add `query_range`, `identity`,
|
|
156
|
+
`check_errors` and `reset` on top of the standard device API.
|
|
157
|
+
|
|
158
|
+
`BaseAptDevice` is the odd one out because APT is not a text protocol. It takes
|
|
159
|
+
a serial `port`, an `AptAxis` destination and a `channel`, and exposes the
|
|
160
|
+
exchange as messages rather than strings: `command(...)` sends one frame,
|
|
161
|
+
`request(...)` sends one and waits for its reply, `hw_info()` and `get_status()`
|
|
162
|
+
read the controller. Motion is handled in the controller's own count domain —
|
|
163
|
+
`home()`, `move_absolute_counts()`, `move_relative_counts()`, `stop()`,
|
|
164
|
+
`get_position_counts()`, `get_velocity_params()` / `set_velocity_params()` —
|
|
165
|
+
and the subclass converts counts to physical units in its user-facing
|
|
166
|
+
operations. Parameter keys are registered with `register_apt_param()` so the
|
|
167
|
+
config system can read and write them like any other. Passing `transport=` a
|
|
168
|
+
`plesty.lib.sim.apt.AptMotorSimulator` runs the whole class without a motor;
|
|
169
|
+
see [Simulation](../sim.md).
|
|
170
|
+
|
|
125
171
|
## State and Synchronization Helpers
|
|
126
172
|
|
|
127
173
|
Useful built-in helpers:
|