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.
Files changed (176) hide show
  1. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/CHANGELOG.md +386 -4
  2. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/PKG-INFO +1 -1
  3. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/analyzer.md +27 -0
  4. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/architecture.md +16 -12
  5. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/base_device.md +46 -0
  6. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/param_system.md +80 -2
  7. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/test_helper.md +67 -3
  8. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/traffic_manager.md +6 -0
  9. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/index.md +8 -5
  10. plesty_lib-0.4.0.dev1/docs/service.md +112 -0
  11. plesty_lib-0.4.0.dev1/docs/sim.md +94 -0
  12. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/toc.yaml +2 -0
  13. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/ui.md +95 -3
  14. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/gl-secret-detection-report.json +7 -7
  15. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/composite_device.py +11 -3
  16. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/device_utils.py +52 -0
  17. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/doc.py +22 -0
  18. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/funcs.py +108 -1
  19. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/params.py +234 -3
  20. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/service/__init__.py +44 -1
  21. plesty_lib-0.4.0.dev1/plesty/lib/service/tcp_ip_server.py +662 -0
  22. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/client_field_test.py +104 -9
  23. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/field_test.py +199 -13
  24. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/__init__.py +9 -2
  25. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/serial.py +28 -3
  26. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/visa.py +417 -27
  27. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/__init__.py +24 -5
  28. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/assets/shell.qss +34 -0
  29. plesty_lib-0.4.0.dev1/plesty/lib/ui/device_link.py +307 -0
  30. plesty_lib-0.4.0.dev1/plesty/lib/ui/device_panel.py +552 -0
  31. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/panel.py +15 -0
  32. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/qt/__init__.py +2 -0
  33. plesty_lib-0.4.0.dev1/plesty/lib/ui/qt/form.py +409 -0
  34. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/qt/shell.py +1 -2
  35. plesty_lib-0.4.0.dev1/tests/manual/stop_closes_the_device.py +116 -0
  36. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_client_field_test.py +63 -1
  37. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_composite_config.py +50 -0
  38. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_funcs.py +106 -0
  39. plesty_lib-0.4.0.dev1/tests/test_device_panel.py +461 -0
  40. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_params.py +215 -1
  41. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_field_test.py +78 -4
  42. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_misc.py +12 -2
  43. plesty_lib-0.4.0.dev1/tests/test_service_loop_factory.py +78 -0
  44. plesty_lib-0.4.0.dev1/tests/test_tcp_ip_concurrent_serve.py +110 -0
  45. plesty_lib-0.4.0.dev1/tests/test_tcp_ip_orphan_reply.py +107 -0
  46. plesty_lib-0.4.0.dev1/tests/test_tcp_ip_stop_signal.py +85 -0
  47. plesty_lib-0.4.0.dev1/tests/test_tcp_ip_traffic.py +196 -0
  48. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_traffic_drivers.py +79 -0
  49. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_visa_discovery.py +308 -0
  50. plesty_lib-0.4.0.dev1/tests/test_visa_mav_gating.py +139 -0
  51. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/uv.lock +52 -15
  52. plesty_lib-0.3.5.dev3/plesty/lib/service/tcp_ip_server.py +0 -363
  53. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/.gitignore +0 -0
  54. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/.gitlab-ci.yml +0 -0
  55. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/LICENSE +0 -0
  56. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/LICENSES/LGPL-3.0-or-later.txt +0 -0
  57. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/README.md +0 -0
  58. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/REUSE.toml +0 -0
  59. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/api_reference.md +0 -0
  60. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/contributing.md +0 -0
  61. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/data_schemas.md +0 -0
  62. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/cmd_solver.md +0 -0
  63. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/composite_device.md +0 -0
  64. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/error_handling.md +0 -0
  65. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/func_system.md +0 -0
  66. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/index.md +0 -0
  67. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/device/logging_system.md +0 -0
  68. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/experiment.md +0 -0
  69. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/imgs/device_layers.svg +0 -0
  70. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/imgs/device_standard.svg +0 -0
  71. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/imgs/plesty_framework.svg +0 -0
  72. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/monitor.md +0 -0
  73. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/docs/quickstart.md +0 -0
  74. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/__init__.py +0 -0
  75. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/analyzer/__init__.py +0 -0
  76. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/analyzer/base_analyzer.py +0 -0
  77. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/data/__init__.py +0 -0
  78. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/data/array.py +0 -0
  79. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/data/ctype_manager.py +0 -0
  80. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/data/io.py +0 -0
  81. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/data/table.py +0 -0
  82. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/data/types.py +0 -0
  83. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/data/units.py +0 -0
  84. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/__init__.py +0 -0
  85. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/async_wrapper.py +0 -0
  86. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/base_apt_device.py +0 -0
  87. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/base_device_sync.py +0 -0
  88. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/base_tcp_scpi_device.py +0 -0
  89. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/base_visa_scpi_device.py +0 -0
  90. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/device/telemetry.py +0 -0
  91. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/__init__.py +0 -0
  92. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/assets/preflight.yaml +0 -0
  93. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/base_experiment.py +0 -0
  94. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/journal.py +0 -0
  95. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/preflight.py +0 -0
  96. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/runs.py +0 -0
  97. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/experiment/schedule.py +0 -0
  98. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/monitor/__init__.py +0 -0
  99. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/monitor/assets/viz.yaml +0 -0
  100. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/monitor/base_monitor.py +0 -0
  101. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/monitor/sources.py +0 -0
  102. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/monitor/viz.py +0 -0
  103. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/service/resource_manager.py +0 -0
  104. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/service/tcp_ip_client.py +0 -0
  105. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/sim/__init__.py +0 -0
  106. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/sim/apt.py +0 -0
  107. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/sim/data_generator.py +0 -0
  108. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/sim/demo_device.py +0 -0
  109. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/solver/__init__.py +0 -0
  110. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/solver/iceblock.py +0 -0
  111. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/solver/scpi.py +0 -0
  112. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/__init__.py +0 -0
  113. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/analyzer_pipeline.py +0 -0
  114. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/device_func_system.py +0 -0
  115. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/device_param_system.py +0 -0
  116. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/device_pipeline.py +0 -0
  117. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/experiment_pipeline.py +0 -0
  118. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/field_test_concurrency.py +0 -0
  119. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/grouped_param_keys.py +0 -0
  120. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/report_artifact.py +0 -0
  121. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/resource_allocation.py +0 -0
  122. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/schema_params.py +0 -0
  123. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/test/schema_refresh.py +0 -0
  124. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/apt.py +0 -0
  125. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/apt_protocol.py +0 -0
  126. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/serial_ports.py +0 -0
  127. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/tcp_ip.py +0 -0
  128. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/usb_utils.py +0 -0
  129. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/traffic/utils.py +0 -0
  130. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/assets/palette.json +0 -0
  131. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/monitor_panel.py +0 -0
  132. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/qt/app.py +0 -0
  133. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/qt/plot.py +0 -0
  134. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/qt/recorder.py +0 -0
  135. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/ui/theme.py +0 -0
  136. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/__init__.py +0 -0
  137. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/config.py +0 -0
  138. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/dll_utils.py +0 -0
  139. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/error_utils.py +0 -0
  140. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/logger.py +0 -0
  141. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/registry.py +0 -0
  142. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/plesty/lib/utils/settings.py +0 -0
  143. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/pyproject.toml +0 -0
  144. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_analyzer.py +0 -0
  145. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_analyzer_pipeline.py +0 -0
  146. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_async_wrapper_threading.py +0 -0
  147. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_config.py +0 -0
  148. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_data_array.py +0 -0
  149. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_data_io_hdf5.py +0 -0
  150. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_data_types.py +0 -0
  151. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_demo_device.py +0 -0
  152. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_apt.py +0 -0
  153. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_base.py +0 -0
  154. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_data_path.py +0 -0
  155. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_pipeline.py +0 -0
  156. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_scpi.py +0 -0
  157. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_device_telemetry.py +0 -0
  158. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_experiment.py +0 -0
  159. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_experiment_pipeline.py +0 -0
  160. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_experiment_preflight.py +0 -0
  161. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_experiment_runs.py +0 -0
  162. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_monitor.py +0 -0
  163. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_report_artifact.py +0 -0
  164. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_schema_params.py +0 -0
  165. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_schema_refresh.py +0 -0
  166. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_service_manager.py +0 -0
  167. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_settings.py +0 -0
  168. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_shipped_imports.py +0 -0
  169. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_sim.py +0 -0
  170. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_solver.py +0 -0
  171. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_tcp_ip_resources.py +0 -0
  172. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_traffic_orphan.py +0 -0
  173. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_traffic_utils.py +0 -0
  174. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_ui.py +0 -0
  175. {plesty_lib-0.3.5.dev3 → plesty_lib-0.4.0.dev1}/tests/test_utils.py +0 -0
  176. {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.4.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.4.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.4.0 the API diff against 0.3.4 (the
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) is a minor bump.
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: plesty-lib
3
- Version: 0.3.5.dev3
3
+ Version: 0.4.0.dev1
4
4
  Summary: A library for the Plesty ecosystem.
5
5
  Author: Plesty Development Team
6
6
  Author-email: Yunshuang Yuan <yunshuang.yuan@fkp.uni-hannover.de>
@@ -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` schema-driven synthetic data for mock solvers and tests. |
28
- | `plesty.lib.test` | Shipped test helpers and the standard contract pipelines (`DevicePipeline`, `ExperimentPipeline`, `AnalyzerPipeline`) that hub modules import. |
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` (ten
147
- gates, SDK gate d1), `ExperimentPipeline` (five hardware-free gates, SDK
148
- gate E1), and `AnalyzerPipeline` (five gates on schema-generated synthetic
149
- inputs, groundwork for SDK gate a1) define what every hub module must prove:
150
- schema integrity, mock round-trips, lifecycle, resource contention (device
151
- tier); deterministic plans, resolvable ops, serializability (experiment
152
- tier); analyze-signature match, round-trip validation, provenance (analyzer
153
- tier). Hub modules expose one pytest function per gate. See
154
- [Test helpers](device/test_helper.md).
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: