plesty-lib 0.3.6__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.6 → plesty_lib-0.4.0.dev1}/CHANGELOG.md +101 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/PKG-INFO +1 -1
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/ui.md +95 -3
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/gl-secret-detection-report.json +5 -5
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/device/composite_device.py +11 -3
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/device/device_utils.py +52 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/device/doc.py +22 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/device/funcs.py +74 -1
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/device/params.py +25 -1
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/service/tcp_ip_server.py +113 -22
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/test/client_field_test.py +104 -9
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/test/field_test.py +199 -13
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/__init__.py +24 -5
- {plesty_lib-0.3.6 → 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.6 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/panel.py +15 -0
- {plesty_lib-0.3.6 → 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.6 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/qt/shell.py +1 -2
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_client_field_test.py +63 -1
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_composite_config.py +50 -0
- plesty_lib-0.4.0.dev1/tests/test_device_panel.py +461 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_field_test.py +78 -4
- plesty_lib-0.4.0.dev1/tests/test_tcp_ip_concurrent_serve.py +110 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_tcp_ip_orphan_reply.py +14 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/uv.lock +13 -13
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/.gitignore +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/.gitlab-ci.yml +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/LICENSE +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/LICENSES/LGPL-3.0-or-later.txt +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/README.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/REUSE.toml +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/analyzer.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/api_reference.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/architecture.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/contributing.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/data_schemas.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/device/base_device.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/device/cmd_solver.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/device/composite_device.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/device/error_handling.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/device/func_system.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/device/index.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/device/logging_system.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/device/param_system.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/device/test_helper.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/device/traffic_manager.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/experiment.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/imgs/device_layers.svg +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/imgs/device_standard.svg +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/imgs/plesty_framework.svg +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/index.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/monitor.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/quickstart.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/service.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/sim.md +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/docs/toc.yaml +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/__init__.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/analyzer/__init__.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/analyzer/base_analyzer.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/data/__init__.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/data/array.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/data/ctype_manager.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/data/io.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/data/table.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/data/types.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/data/units.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/device/__init__.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/device/async_wrapper.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/device/base_apt_device.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/device/base_device_sync.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/device/base_tcp_scpi_device.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/device/base_visa_scpi_device.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/device/telemetry.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/__init__.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/assets/preflight.yaml +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/base_experiment.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/journal.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/preflight.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/runs.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/schedule.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/monitor/__init__.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/monitor/assets/viz.yaml +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/monitor/base_monitor.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/monitor/sources.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/monitor/viz.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/service/__init__.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/service/resource_manager.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/service/tcp_ip_client.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/sim/__init__.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/sim/apt.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/sim/data_generator.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/sim/demo_device.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/solver/__init__.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/solver/iceblock.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/solver/scpi.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/test/__init__.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/test/analyzer_pipeline.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/test/device_func_system.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/test/device_param_system.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/test/device_pipeline.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/test/experiment_pipeline.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/test/field_test_concurrency.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/test/grouped_param_keys.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/test/report_artifact.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/test/resource_allocation.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/test/schema_params.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/test/schema_refresh.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/__init__.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/apt.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/apt_protocol.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/serial.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/serial_ports.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/tcp_ip.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/usb_utils.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/utils.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/visa.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/assets/palette.json +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/monitor_panel.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/qt/app.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/qt/plot.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/qt/recorder.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/theme.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/__init__.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/config.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/dll_utils.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/error_utils.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/logger.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/registry.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/settings.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/pyproject.toml +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/manual/stop_closes_the_device.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_analyzer.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_analyzer_pipeline.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_async_wrapper_threading.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_config.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_data_array.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_data_io_hdf5.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_data_types.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_demo_device.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_device_apt.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_device_base.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_device_data_path.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_device_funcs.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_device_params.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_device_pipeline.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_device_scpi.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_device_telemetry.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_experiment.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_experiment_pipeline.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_experiment_preflight.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_experiment_runs.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_misc.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_monitor.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_report_artifact.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_schema_params.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_schema_refresh.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_service_loop_factory.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_service_manager.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_settings.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_shipped_imports.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_sim.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_solver.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_tcp_ip_resources.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_tcp_ip_stop_signal.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_tcp_ip_traffic.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_traffic_drivers.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_traffic_orphan.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_traffic_utils.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_ui.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_utils.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_visa_discovery.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_visa_mav_gating.py +0 -0
- {plesty_lib-0.3.6 → plesty_lib-0.4.0.dev1}/tests/test_viz.py +0 -0
|
@@ -2,6 +2,107 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
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
|
+
|
|
5
106
|
- **A client holding an allocation can call schema operations again, and those
|
|
6
107
|
calls are now access-controlled.** The server appends the caller's allocation
|
|
7
108
|
to every `call` it forwards, but a schema operation is a generated closure
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
`plesty.lib.ui` is the GUI framework of the platform: **one home window that
|
|
4
4
|
hosts any number of rearrangeable sub-windows**. It is deliberately generic —
|
|
5
|
-
a live view
|
|
6
|
-
|
|
5
|
+
a live view (`MonitorPanel`) and a device's controls (`DevicePanel`) are the
|
|
6
|
+
same contract in the same window, and a run status board would be a third.
|
|
7
7
|
|
|
8
8
|
The framework is toolkit-free down to the last layer. `Panel`, `Theme`, and
|
|
9
9
|
`MonitorPanel` import without a GUI installed; only the Qt backend needs the
|
|
@@ -72,11 +72,98 @@ Placement (`area`, `floating`, `closable`, `min_width`, `min_height`) is
|
|
|
72
72
|
declared at construction and is only the *initial* arrangement — the operator
|
|
73
73
|
owns it from there.
|
|
74
74
|
|
|
75
|
+
`status()` is the line in the title bar and `tone()` the colour of its dot —
|
|
76
|
+
`running`, `idle`, `warning`, `error`. The default reads a panel as working
|
|
77
|
+
whenever it has something to say, which is right for a view that only draws; a
|
|
78
|
+
panel that can fail *while still running* overrides it, so a device that
|
|
79
|
+
stopped answering is red from across the room.
|
|
80
|
+
|
|
75
81
|
`MonitorPanel` is the bridge to the [monitor framework](monitor.md): it
|
|
76
82
|
attaches a `PlotRenderer` to a monitor when it is built, and ticks it
|
|
77
83
|
afterwards. Constructing and describing it needs no GUI, so a shell's
|
|
78
84
|
composition is testable headless.
|
|
79
85
|
|
|
86
|
+
## Device panels
|
|
87
|
+
|
|
88
|
+
A device already describes its controls completely, so `DevicePanel` builds
|
|
89
|
+
them instead of asking anyone to write them again:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from plesty.lib.ui import DevicePanel, LocalHandle, run
|
|
93
|
+
|
|
94
|
+
run([DevicePanel(LocalHandle(spectrometer), name="cct",
|
|
95
|
+
include=("exposure", "hardware_average"))])
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Every widget comes from the schema. Nothing is hand-placed, and nothing is
|
|
99
|
+
hand-bounded:
|
|
100
|
+
|
|
101
|
+
| The device declares | The panel renders |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `dtype` `float`/`int` | spin box, adaptive step |
|
|
104
|
+
| `min_value` / `max_value` | its range — and where the device declares none, none |
|
|
105
|
+
| `options` | drop-down |
|
|
106
|
+
| `dtype` `bool` | check box |
|
|
107
|
+
| `read_only` | a readout, not an editor |
|
|
108
|
+
| `write_only` | an editor that is never polled |
|
|
109
|
+
| `unit` | the editor's suffix |
|
|
110
|
+
| `description` | the tooltip |
|
|
111
|
+
| an operation | a button, with a small form when it takes arguments |
|
|
112
|
+
|
|
113
|
+
That table is the whole point. A panel written by hand for one spectrometer
|
|
114
|
+
bounded its exposure at `0.01–60000 ms` where the device declares no upper
|
|
115
|
+
bound at all, and its hardware averaging at `1–1000` where the device declares
|
|
116
|
+
no limit — numbers that exist nowhere in the instrument, constraining whoever
|
|
117
|
+
used it. `controls_for()` derives them, is free of any toolkit, and is tested
|
|
118
|
+
without a display, so a drift like that fails a test instead of reaching a
|
|
119
|
+
bench.
|
|
120
|
+
|
|
121
|
+
**What stays out.** A spectrum, a beam image, a stage map are not derivable
|
|
122
|
+
from a schema — those are monitors, and a `MonitorPanel` docks beside this one.
|
|
123
|
+
Operations declared `lifecycle` (homing, reset, replacing a dark reference) are
|
|
124
|
+
left off unless named in `operations=`: they should not be one click away.
|
|
125
|
+
|
|
126
|
+
**A panel starts read-only.** A monitor holds no device; this one necessarily
|
|
127
|
+
does, and the instrument may be driven by a running experiment. Editors and
|
|
128
|
+
buttons stay disabled behind the panel's *Take control* toggle
|
|
129
|
+
(`set_writable()`).
|
|
130
|
+
|
|
131
|
+
### Where the device is
|
|
132
|
+
|
|
133
|
+
`DevicePanel` never talks to a device directly — it drives a `DeviceHandle`,
|
|
134
|
+
which is four calls: `describe()`, `query(key)`, `write(key, value)` and
|
|
135
|
+
`call(op, **kwargs)`. That is what lets one panel serve three places:
|
|
136
|
+
|
|
137
|
+
| | The handle |
|
|
138
|
+
|---|---|
|
|
139
|
+
| A module's own GUI | `LocalHandle(device)` |
|
|
140
|
+
| A console beside the device server | `DeviceTCPIPClient` — it satisfies the protocol as written |
|
|
141
|
+
| Another machine | the same four calls, relayed by the bench agent |
|
|
142
|
+
|
|
143
|
+
`describe` carries the device's serialized `DeviceDocModel` — units, bounds,
|
|
144
|
+
options, descriptions — beside the method list it always returned, so a caller
|
|
145
|
+
that never imports plesty-lib reads what the device declared instead of
|
|
146
|
+
inventing it. A server older than that payload answers with method names only,
|
|
147
|
+
and the panel says so rather than showing an empty form.
|
|
148
|
+
|
|
149
|
+
`LocalHandle` goes through the same serialized payload the wire uses. That is
|
|
150
|
+
deliberate: a module's own GUI then exercises exactly what the console and a
|
|
151
|
+
remote client will see, so a hole in the payload shows up at the bench and not
|
|
152
|
+
only across a network.
|
|
153
|
+
|
|
154
|
+
### Why it never reads on the GUI thread
|
|
155
|
+
|
|
156
|
+
Panels are ticked on the shell's clock, and a device read is a round trip to
|
|
157
|
+
hardware. Every call therefore goes to a `DeviceWorker` — one thread per panel
|
|
158
|
+
— and every result comes back through a queue the panel drains on its next
|
|
159
|
+
tick, so the shell's own clock does the hand-over and no Qt signal is involved.
|
|
160
|
+
|
|
161
|
+
One worker *per panel*, not per shell: a device server that has died answers
|
|
162
|
+
nothing until its client times out, and a shared worker would let that one
|
|
163
|
+
device stall every panel in the window. Requests are coalesced by tag, so a
|
|
164
|
+
device slower than the refresh interval drops the polls it cannot keep up with
|
|
165
|
+
instead of accumulating a backlog and showing readings from minutes ago.
|
|
166
|
+
|
|
80
167
|
## Theme
|
|
81
168
|
|
|
82
169
|
Colours are data, not code. `Theme` reads `assets/palette.json` and renders
|
|
@@ -135,9 +222,14 @@ skipped where the `gui` extra is not installed, which is why CI (and any
|
|
|
135
222
|
headless lab host) still runs the full suite:
|
|
136
223
|
|
|
137
224
|
```bash
|
|
138
|
-
QT_QPA_PLATFORM=offscreen uv run pytest tests/test_ui.py
|
|
225
|
+
QT_QPA_PLATFORM=offscreen uv run pytest tests/test_ui.py tests/test_device_panel.py
|
|
139
226
|
```
|
|
140
227
|
|
|
228
|
+
A device panel is tested the same way, and further: `open()` starts its worker
|
|
229
|
+
without building a widget, so the whole device-facing half — the schema
|
|
230
|
+
arriving, polling, a refused write, a device that stops answering — is driven
|
|
231
|
+
with no display and no toolkit at all.
|
|
232
|
+
|
|
141
233
|
## See also
|
|
142
234
|
|
|
143
235
|
- [Monitor framework](monitor.md) — what `MonitorPanel` shows.
|
|
@@ -19,19 +19,19 @@
|
|
|
19
19
|
"version": "8.30.1"
|
|
20
20
|
},
|
|
21
21
|
"type": "secret_detection",
|
|
22
|
-
"start_time": "2026-
|
|
23
|
-
"end_time": "2026-
|
|
22
|
+
"start_time": "2026-09-03T11:41:33",
|
|
23
|
+
"end_time": "2026-09-03T11:41:34",
|
|
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.518565688,
|
|
30
30
|
"exit_code": 0,
|
|
31
31
|
"git_strategy": "FetchShallow",
|
|
32
|
-
"repo_size_kb":
|
|
32
|
+
"repo_size_kb": 767,
|
|
33
33
|
"commit_count": 1,
|
|
34
|
-
"bytes_scanned":
|
|
34
|
+
"bytes_scanned": 76550,
|
|
35
35
|
"pipeline_type": "Tag"
|
|
36
36
|
},
|
|
37
37
|
{
|
|
@@ -719,12 +719,20 @@ class CompositeDevice:
|
|
|
719
719
|
self._set_state(dev, ConnectionState.CONNECTED, "reconnected")
|
|
720
720
|
|
|
721
721
|
def identity(self) -> dict[str, Any]:
|
|
722
|
-
"""Query the identity of each sub-device and return a mapping of results.
|
|
722
|
+
"""Query the identity of each sub-device and return a mapping of results.
|
|
723
|
+
|
|
724
|
+
Routed through :meth:`submit` like every other sub-device call, not
|
|
725
|
+
called on the client directly. Under ``thread_affinity`` a sub-device's
|
|
726
|
+
socket belongs to its own thread, and asking here from a foreign one
|
|
727
|
+
put a second thread on it: a magneto-PL rig whose power regulator was
|
|
728
|
+
sampling the meter got the identity string back from ``measure_power``
|
|
729
|
+
(2026-09-01). The crash was the lucky case — two calls of the same
|
|
730
|
+
shape crossing swap two readings and say nothing.
|
|
731
|
+
"""
|
|
723
732
|
identities = {}
|
|
724
733
|
for device_name in self.devices:
|
|
725
|
-
device = getattr(self, device_name)
|
|
726
734
|
try:
|
|
727
|
-
identities[device_name] =
|
|
735
|
+
identities[device_name] = self.submit(device_name, "identity").result()
|
|
728
736
|
except Exception as e:
|
|
729
737
|
identities[device_name] = f"Error: {e}"
|
|
730
738
|
return identities
|
|
@@ -31,6 +31,58 @@ class ResponseParser:
|
|
|
31
31
|
)
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
def dtype_name(dtype: Any) -> str:
|
|
35
|
+
"""Return the wire name of a declared data type.
|
|
36
|
+
|
|
37
|
+
A schema declares a type as a Python type, a typing construct, or already
|
|
38
|
+
as a string. A caller on the far side of the device protocol has to switch
|
|
39
|
+
on it, so it arrives as a name and never as a ``<class 'float'>`` repr.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
dtype: The declared type, or ``None``.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
The type's name; ``"Any"`` when nothing was declared.
|
|
46
|
+
"""
|
|
47
|
+
if dtype is None:
|
|
48
|
+
return "Any"
|
|
49
|
+
if isinstance(dtype, str):
|
|
50
|
+
return dtype
|
|
51
|
+
name = getattr(dtype, "__name__", None)
|
|
52
|
+
return name if name else str(dtype).replace("typing.", "")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def jsonable(value: Any) -> Any:
|
|
56
|
+
"""Return *value* as something :func:`json.dumps` accepts.
|
|
57
|
+
|
|
58
|
+
Schema defaults, options and bounds are whatever the module declared — a
|
|
59
|
+
numpy scalar, an enum member, a tuple. They cross the wire as
|
|
60
|
+
documentation, so a value JSON has no form for is described rather than
|
|
61
|
+
dropped: a reader learns what the default is even when it cannot be one.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
value: The value to convert.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
The value unchanged when JSON already holds it, a list or dict when it
|
|
68
|
+
is a container, else its ``str()``.
|
|
69
|
+
"""
|
|
70
|
+
if value is None or isinstance(value, (bool, int, float, str)):
|
|
71
|
+
return value
|
|
72
|
+
if isinstance(value, (list, tuple, set, frozenset)):
|
|
73
|
+
return [jsonable(item) for item in value]
|
|
74
|
+
if isinstance(value, dict):
|
|
75
|
+
return {str(key): jsonable(item) for key, item in value.items()}
|
|
76
|
+
# A numpy scalar is not an int or a str but holds one.
|
|
77
|
+
item = getattr(value, "item", None)
|
|
78
|
+
if callable(item):
|
|
79
|
+
try:
|
|
80
|
+
return jsonable(item())
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
return str(value)
|
|
84
|
+
|
|
85
|
+
|
|
34
86
|
# define decoretor for device methods that require operatability check
|
|
35
87
|
def operatable(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
36
88
|
"""Decorator that checks device operatability before executing the wrapped function."""
|
|
@@ -14,6 +14,7 @@ it instead of re-deriving the metadata.
|
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
from dataclasses import dataclass
|
|
17
|
+
from typing import Any
|
|
17
18
|
|
|
18
19
|
from plesty.lib.device.funcs import FuncDoc
|
|
19
20
|
from plesty.lib.device.params import ConfigParameter
|
|
@@ -35,3 +36,24 @@ class DeviceDocModel:
|
|
|
35
36
|
parameters: dict[str, list[ConfigParameter]]
|
|
36
37
|
functions: list[FuncDoc]
|
|
37
38
|
standard_methods: list[FuncDoc]
|
|
39
|
+
|
|
40
|
+
def to_dict(self) -> dict[str, Any]:
|
|
41
|
+
"""Return the whole model as JSON primitives.
|
|
42
|
+
|
|
43
|
+
This is what leaves the device: the ``describe`` reply carries it, so a
|
|
44
|
+
caller that never imports plesty-lib — a console on another process, a
|
|
45
|
+
client on another machine — reads the same units, bounds, options and
|
|
46
|
+
descriptions the device declared, instead of inventing them.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
A mapping with ``parameters`` (grouped, as the model holds them),
|
|
50
|
+
``functions`` and ``standard_methods``, all JSON primitives.
|
|
51
|
+
"""
|
|
52
|
+
return {
|
|
53
|
+
"parameters": {
|
|
54
|
+
group: [param.to_dict() for param in params]
|
|
55
|
+
for group, params in self.parameters.items()
|
|
56
|
+
},
|
|
57
|
+
"functions": [func.to_dict() for func in self.functions],
|
|
58
|
+
"standard_methods": [func.to_dict() for func in self.standard_methods],
|
|
59
|
+
}
|
|
@@ -14,7 +14,13 @@ import re
|
|
|
14
14
|
import reprlib
|
|
15
15
|
import numpy as np
|
|
16
16
|
|
|
17
|
-
from plesty.lib.device.device_utils import
|
|
17
|
+
from plesty.lib.device.device_utils import (
|
|
18
|
+
OpKind,
|
|
19
|
+
ResponseParser,
|
|
20
|
+
dtype_name,
|
|
21
|
+
jsonable,
|
|
22
|
+
op_kind_of,
|
|
23
|
+
)
|
|
18
24
|
from plesty.lib.data import (
|
|
19
25
|
PlestyArray,
|
|
20
26
|
PlestyTable2D,
|
|
@@ -161,6 +167,26 @@ class FuncParam:
|
|
|
161
167
|
item_dtype: Any = field(default=None)
|
|
162
168
|
description: str = field(default="")
|
|
163
169
|
|
|
170
|
+
def to_dict(self) -> dict[str, Any]:
|
|
171
|
+
"""Return this argument as JSON primitives.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Name, type, unit, default and constraints — what a caller needs to
|
|
175
|
+
offer the argument and check it before sending.
|
|
176
|
+
"""
|
|
177
|
+
return {
|
|
178
|
+
"name": self.name,
|
|
179
|
+
"dtype": dtype_name(self.dtype),
|
|
180
|
+
"unit": self.unit,
|
|
181
|
+
"default": jsonable(self.default),
|
|
182
|
+
"required": self.required,
|
|
183
|
+
"options": jsonable(self.options),
|
|
184
|
+
"range": jsonable(self.range),
|
|
185
|
+
"shape": jsonable(self.shape),
|
|
186
|
+
"item_dtype": dtype_name(self.item_dtype) if self.item_dtype is not None else None,
|
|
187
|
+
"description": self.description,
|
|
188
|
+
}
|
|
189
|
+
|
|
164
190
|
|
|
165
191
|
@dataclass
|
|
166
192
|
class FuncOutput:
|
|
@@ -177,6 +203,35 @@ class FuncOutput:
|
|
|
177
203
|
headers: list[TableHeader] | None = field(default=None)
|
|
178
204
|
description: str = field(default="")
|
|
179
205
|
|
|
206
|
+
def to_dict(self) -> dict[str, Any]:
|
|
207
|
+
"""Return this output as JSON primitives.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Name, type, unit and shape — what a caller needs to read the
|
|
211
|
+
result, including a table's column headers.
|
|
212
|
+
"""
|
|
213
|
+
return {
|
|
214
|
+
"name": self.name,
|
|
215
|
+
"dtype": dtype_name(self.dtype),
|
|
216
|
+
"unit": self.unit,
|
|
217
|
+
"required": self.required,
|
|
218
|
+
"code_mapping": jsonable(self.code_mapping),
|
|
219
|
+
"range": jsonable(self.range),
|
|
220
|
+
"shape": jsonable(self.shape),
|
|
221
|
+
"item_dtype": dtype_name(self.item_dtype) if self.item_dtype is not None else None,
|
|
222
|
+
"headers": [
|
|
223
|
+
{
|
|
224
|
+
"name": header.name,
|
|
225
|
+
"dtype": dtype_name(header.dtype),
|
|
226
|
+
"unit": header.unit,
|
|
227
|
+
"description": header.description,
|
|
228
|
+
}
|
|
229
|
+
for header in self.headers or []
|
|
230
|
+
]
|
|
231
|
+
or None,
|
|
232
|
+
"description": self.description,
|
|
233
|
+
}
|
|
234
|
+
|
|
180
235
|
|
|
181
236
|
@dataclass
|
|
182
237
|
class FuncDoc:
|
|
@@ -204,6 +259,24 @@ class FuncDoc:
|
|
|
204
259
|
oparams: list["FuncOutput"]
|
|
205
260
|
kind: OpKind = field(default_factory=OpKind)
|
|
206
261
|
|
|
262
|
+
def to_dict(self) -> dict[str, Any]:
|
|
263
|
+
"""Return this operation as JSON primitives.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
The operation with its arguments, its outputs and its
|
|
267
|
+
:class:`~plesty.lib.device.device_utils.OpKind` — enough for a
|
|
268
|
+
caller to offer it, and to decide whether it should be one click
|
|
269
|
+
away.
|
|
270
|
+
"""
|
|
271
|
+
return {
|
|
272
|
+
"name": self.name,
|
|
273
|
+
"source": self.source,
|
|
274
|
+
"description": self.description,
|
|
275
|
+
"iparams": [param.to_dict() for param in self.iparams],
|
|
276
|
+
"oparams": [output.to_dict() for output in self.oparams],
|
|
277
|
+
"kind": self.kind.to_dict(),
|
|
278
|
+
}
|
|
279
|
+
|
|
207
280
|
|
|
208
281
|
#: Size-bounded repr for operation log lines, so array-valued parameters do
|
|
209
282
|
#: not flood the log.
|
|
@@ -12,7 +12,7 @@ import logging
|
|
|
12
12
|
import re
|
|
13
13
|
|
|
14
14
|
from plesty.lib.utils.error_utils import handle_error
|
|
15
|
-
from plesty.lib.device.device_utils import ResponseParser
|
|
15
|
+
from plesty.lib.device.device_utils import ResponseParser, dtype_name, jsonable
|
|
16
16
|
from plesty.lib.data import resolve_dtype, istype, cast_basic_type
|
|
17
17
|
|
|
18
18
|
|
|
@@ -271,6 +271,30 @@ class ConfigParameter:
|
|
|
271
271
|
parser: ResponseParser | None = None
|
|
272
272
|
description: str | None = None
|
|
273
273
|
|
|
274
|
+
def to_dict(self) -> dict[str, Any]:
|
|
275
|
+
"""Return the caller-facing description of this parameter.
|
|
276
|
+
|
|
277
|
+
Everything needed to render or bound the parameter, and nothing about
|
|
278
|
+
how it reaches the instrument: ``command`` and ``parser`` are this
|
|
279
|
+
device's own business, and neither has a JSON form.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
The parameter as JSON primitives.
|
|
283
|
+
"""
|
|
284
|
+
return {
|
|
285
|
+
"name": self.name,
|
|
286
|
+
"dtype": dtype_name(self.dtype),
|
|
287
|
+
"unit": self.unit,
|
|
288
|
+
"default": jsonable(self.default),
|
|
289
|
+
"value": jsonable(self.value),
|
|
290
|
+
"read_only": self.read_only,
|
|
291
|
+
"write_only": self.write_only,
|
|
292
|
+
"min_value": jsonable(self.min_value),
|
|
293
|
+
"max_value": jsonable(self.max_value),
|
|
294
|
+
"options": jsonable(self.options),
|
|
295
|
+
"description": self.description,
|
|
296
|
+
}
|
|
297
|
+
|
|
274
298
|
|
|
275
299
|
@dataclass
|
|
276
300
|
class ConfigGroup:
|