seq-quince 0.1.1__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.
- seq_quince-0.1.1/CHANGELOG.md +16 -0
- seq_quince-0.1.1/CONTRIBUTING.md +47 -0
- seq_quince-0.1.1/LICENSE +21 -0
- seq_quince-0.1.1/MANIFEST.in +1 -0
- seq_quince-0.1.1/Makefile +110 -0
- seq_quince-0.1.1/PKG-INFO +145 -0
- seq_quince-0.1.1/README.md +102 -0
- seq_quince-0.1.1/bash-completion/.gitkeep +0 -0
- seq_quince-0.1.1/bash-completion/device_runner +19 -0
- seq_quince-0.1.1/bash-completion/scheduler_runner +111 -0
- seq_quince-0.1.1/docs/device_runner.md +84 -0
- seq_quince-0.1.1/docs/devices/README-actuator.md +71 -0
- seq_quince-0.1.1/docs/devices/README-hexapod.md +125 -0
- seq_quince-0.1.1/docs/devices/README-mono-zero-order.md +126 -0
- seq_quince-0.1.1/docs/devices/README-ophyd-motor.md +45 -0
- seq_quince-0.1.1/docs/devices/README-scan.md +86 -0
- seq_quince-0.1.1/docs/devices/README-sequence.md +71 -0
- seq_quince-0.1.1/docs/devices/README-sim.md +44 -0
- seq_quince-0.1.1/docs/devices/README-toggle.md +59 -0
- seq_quince-0.1.1/docs/devices/README-wait-condition.md +124 -0
- seq_quince-0.1.1/docs/devices.md +91 -0
- seq_quince-0.1.1/docs/misc/symbols.md +35 -0
- seq_quince-0.1.1/docs/resource-lifecycle.md +3 -0
- seq_quince-0.1.1/examples/beamline_env.sh +11 -0
- seq_quince-0.1.1/pyproject.toml +136 -0
- seq_quince-0.1.1/setup.cfg +4 -0
- seq_quince-0.1.1/src/config/beamlines/.gitkeep +0 -0
- seq_quince-0.1.1/src/config/common/device_templates/.gitkeep +0 -0
- seq_quince-0.1.1/src/seq_quince/__init__.py +0 -0
- seq_quince-0.1.1/src/seq_quince/config_manager.py +129 -0
- seq_quince-0.1.1/src/seq_quince/device/__init__.py +0 -0
- seq_quince-0.1.1/src/seq_quince/device/device_abs.py +96 -0
- seq_quince-0.1.1/src/seq_quince/device/extensions.py +211 -0
- seq_quince-0.1.1/src/seq_quince/device/factory.py +75 -0
- seq_quince-0.1.1/src/seq_quince/device/lifecycle.py +417 -0
- seq_quince-0.1.1/src/seq_quince/device_concrete/__init__.py +0 -0
- seq_quince-0.1.1/src/seq_quince/device_concrete/_option_lookup.py +42 -0
- seq_quince-0.1.1/src/seq_quince/device_concrete/actuator.py +88 -0
- seq_quince-0.1.1/src/seq_quince/device_concrete/hexapod.py +210 -0
- seq_quince-0.1.1/src/seq_quince/device_concrete/mono_zero_order.py +528 -0
- seq_quince-0.1.1/src/seq_quince/device_concrete/ophyd_motor.py +85 -0
- seq_quince-0.1.1/src/seq_quince/device_concrete/scan.py +195 -0
- seq_quince-0.1.1/src/seq_quince/device_concrete/sequence.py +189 -0
- seq_quince-0.1.1/src/seq_quince/device_concrete/sim.py +67 -0
- seq_quince-0.1.1/src/seq_quince/device_concrete/toggle.py +124 -0
- seq_quince-0.1.1/src/seq_quince/device_concrete/wait_condition.py +536 -0
- seq_quince-0.1.1/src/seq_quince/device_runner.py +71 -0
- seq_quince-0.1.1/src/seq_quince/loader.py +52 -0
- seq_quince-0.1.1/src/seq_quince/runner.py +230 -0
- seq_quince-0.1.1/src/seq_quince/scheduler.py +314 -0
- seq_quince-0.1.1/src/seq_quince/scheduler_check_devices.py +71 -0
- seq_quince-0.1.1/src/seq_quince/scheduler_runner.py +115 -0
- seq_quince-0.1.1/src/seq_quince/sequence.py +101 -0
- seq_quince-0.1.1/src/seq_quince/snapshot.py +116 -0
- seq_quince-0.1.1/src/seq_quince/tools.py +69 -0
- seq_quince-0.1.1/src/seq_quince.egg-info/PKG-INFO +145 -0
- seq_quince-0.1.1/src/seq_quince.egg-info/SOURCES.txt +93 -0
- seq_quince-0.1.1/src/seq_quince.egg-info/dependency_links.txt +1 -0
- seq_quince-0.1.1/src/seq_quince.egg-info/entry_points.txt +3 -0
- seq_quince-0.1.1/src/seq_quince.egg-info/requires.txt +14 -0
- seq_quince-0.1.1/src/seq_quince.egg-info/scm_file_list.json +93 -0
- seq_quince-0.1.1/src/seq_quince.egg-info/scm_version.json +8 -0
- seq_quince-0.1.1/src/seq_quince.egg-info/top_level.txt +1 -0
- seq_quince-0.1.1/tests/core_units/__init__.py +1 -0
- seq_quince-0.1.1/tests/core_units/_support.py +51 -0
- seq_quince-0.1.1/tests/core_units/test_config_manager.py +291 -0
- seq_quince-0.1.1/tests/core_units/test_device_execution.py +412 -0
- seq_quince-0.1.1/tests/core_units/test_device_runner.py +103 -0
- seq_quince-0.1.1/tests/core_units/test_loader.py +120 -0
- seq_quince-0.1.1/tests/core_units/test_motion_tracking.py +108 -0
- seq_quince-0.1.1/tests/core_units/test_resource_cleanup.py +500 -0
- seq_quince-0.1.1/tests/core_units/test_runner.py +235 -0
- seq_quince-0.1.1/tests/core_units/test_runner_executor.py +158 -0
- seq_quince-0.1.1/tests/core_units/test_scheduler.py +286 -0
- seq_quince-0.1.1/tests/core_units/test_scheduler_check_devices.py +211 -0
- seq_quince-0.1.1/tests/core_units/test_scheduler_runner.py +125 -0
- seq_quince-0.1.1/tests/core_units/test_scheduler_safety.py +691 -0
- seq_quince-0.1.1/tests/core_units/test_sequence_builder.py +79 -0
- seq_quince-0.1.1/tests/core_units/test_snapshot.py +301 -0
- seq_quince-0.1.1/tests/core_units/test_snapshot_cleanup_safety.py +110 -0
- seq_quince-0.1.1/tests/core_units/test_tools.py +123 -0
- seq_quince-0.1.1/tests/device_units/__init__.py +1 -0
- seq_quince-0.1.1/tests/device_units/_support.py +114 -0
- seq_quince-0.1.1/tests/device_units/test_actuator_device.py +172 -0
- seq_quince-0.1.1/tests/device_units/test_async_hardware.py +388 -0
- seq_quince-0.1.1/tests/device_units/test_device_contract.py +106 -0
- seq_quince-0.1.1/tests/device_units/test_hexapod_device.py +537 -0
- seq_quince-0.1.1/tests/device_units/test_mono_zero_order.py +397 -0
- seq_quince-0.1.1/tests/device_units/test_ophyd_motor_device.py +50 -0
- seq_quince-0.1.1/tests/device_units/test_resource_cleanup.py +175 -0
- seq_quince-0.1.1/tests/device_units/test_scan_device.py +435 -0
- seq_quince-0.1.1/tests/device_units/test_sequence_device.py +156 -0
- seq_quince-0.1.1/tests/device_units/test_sim_device.py +81 -0
- seq_quince-0.1.1/tests/device_units/test_toggle_device.py +58 -0
- seq_quince-0.1.1/tests/device_units/test_wait_condition_device.py +385 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `seq-quince` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-11-13
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Initial standalone package, extracted from the `monopy` `src/scheduler` sub-package.
|
|
14
|
+
- Python imports use `seq_quince`.
|
|
15
|
+
- Console scripts: `scheduler_runner` and `device_runner`.
|
|
16
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Contributing to seq_quince
|
|
2
|
+
|
|
3
|
+
Seq-quince runs configurable device sequences for beamline operation using asyncio,
|
|
4
|
+
EPICS, and kiwi-scan. These guidelines are written for contributors and AI agents.
|
|
5
|
+
|
|
6
|
+
## Making changes
|
|
7
|
+
|
|
8
|
+
- Read the relevant implementation, configuration, and tests before editing.
|
|
9
|
+
- Keep changes focused. Preserve unrelated work and avoid incidental refactoring.
|
|
10
|
+
- Preserve public APIs, YAML keys, defaults, and execution order unless the task
|
|
11
|
+
explicitly changes them. Document intentional behavior changes.
|
|
12
|
+
- Keep Python 3.8 compatibility. For example, use `run_in_executor()` instead of
|
|
13
|
+
`asyncio.to_thread()`.
|
|
14
|
+
- Comment on context, e.g. reasons or hardware assumptions that the code cannot explain.
|
|
15
|
+
- User output can use unicode symbols from doc/misc/symbols.md for better readability
|
|
16
|
+
|
|
17
|
+
## Devices and scheduling
|
|
18
|
+
|
|
19
|
+
- Default device implementations are located in `src/seq_quince/device_concrete/`. Follow
|
|
20
|
+
`DeviceABC` in `src/seq_quince/device/device_abs.py` and register new device types
|
|
21
|
+
through `DeviceFactory` in `src/seq_quince/device/factory.py`.
|
|
22
|
+
- New devices are usually tested as external device, register them with DEVICE_CLASSES and
|
|
23
|
+
set the SEQ_QUINCE_DEVICE_PATH environment variable.
|
|
24
|
+
- Keep blocking hardware operations out of the asyncio event loop. Preserve
|
|
25
|
+
cancellation, stop, cleanup, and rollback behavior.
|
|
26
|
+
- Logging: lazy formatting, and restrained output in polling loops.
|
|
27
|
+
|
|
28
|
+
## Validation
|
|
29
|
+
|
|
30
|
+
Run:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
make lint && make test
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Unit tests
|
|
37
|
+
|
|
38
|
+
Unittests are located in tests. Use unittest.TestCase (CoreTestCase or DeviceTestCase)
|
|
39
|
+
Do not use real CA.
|
|
40
|
+
|
|
41
|
+
## Notes
|
|
42
|
+
|
|
43
|
+
- Code must run on Python 3.8: no `match`, no `list[int]` at runtime, no parenthesized context managers, no `str.removeprefix`.
|
|
44
|
+
Check both syntax and standard-library availability.
|
|
45
|
+
- Keep CLI layers thin. Put reusable behavior in library functions and reuse existing configuration models, readers, and formatters.
|
|
46
|
+
- Verify the change. Add focused tests.
|
|
47
|
+
- Validate configuration before the run starts.
|
seq_quince-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Helmholtz-Zentrum Berlin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
exclude CLAUDE.md AGENTS.md mkvenv.sh .gitignore
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
SHELL := /bin/bash
|
|
2
|
+
VERSION ?= 0.1.0
|
|
3
|
+
.DEFAULT_GOAL := help
|
|
4
|
+
|
|
5
|
+
BASH_COMPLETION_DIR ?= ~/.bash_completion.d
|
|
6
|
+
WITH_VENV = if [ -z "$$VIRTUAL_ENV" ]; then source "$(CURDIR)/mkvenv.sh"; fi
|
|
7
|
+
|
|
8
|
+
# set COMMIT for tagging
|
|
9
|
+
COMMIT ?= HEAD
|
|
10
|
+
|
|
11
|
+
# Completion files expected in bash-completion/ (copy them over from monopy/bash-completion).
|
|
12
|
+
SCRIPTS = \
|
|
13
|
+
scheduler_runner \
|
|
14
|
+
device_runner
|
|
15
|
+
# ...
|
|
16
|
+
|
|
17
|
+
SCRIPT_PATHS = $(foreach script,$(SCRIPTS),$(BASH_COMPLETION_DIR)/$(script))
|
|
18
|
+
|
|
19
|
+
.PHONY: help all install_completion uninstall_completion clean cscope tag lint test build
|
|
20
|
+
|
|
21
|
+
help: ## Show available development targets
|
|
22
|
+
@echo "seq_quince development targets"
|
|
23
|
+
@echo
|
|
24
|
+
@awk 'BEGIN {FS = ":.*## "} /^[a-zA-Z0-9_.-]+:.*## / { printf " %-22s %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
|
|
25
|
+
|
|
26
|
+
all: install_completion ## Install bash completion scripts
|
|
27
|
+
|
|
28
|
+
install_completion: ## Install bash completion files and add source lines to ~/.bashrc
|
|
29
|
+
@mkdir -p $(BASH_COMPLETION_DIR)
|
|
30
|
+
@for script in $(SCRIPTS); do \
|
|
31
|
+
if [ ! -f bash-completion/$$script ]; then echo "Skipping $$script (not found in bash-completion/)"; continue; fi; \
|
|
32
|
+
cp bash-completion/$$script $(BASH_COMPLETION_DIR)/$$script; \
|
|
33
|
+
grep -q "$$script" ~/.bashrc || echo "source $(BASH_COMPLETION_DIR)/$$script" >> ~/.bashrc; \
|
|
34
|
+
done
|
|
35
|
+
@echo "Bash completion for $(SCRIPTS) installed. Reload your shell to activate."
|
|
36
|
+
|
|
37
|
+
uninstall_completion: ## Remove installed bash completion files
|
|
38
|
+
@for script in $(SCRIPTS); do \
|
|
39
|
+
rm -f $(BASH_COMPLETION_DIR)/$$script; \
|
|
40
|
+
done
|
|
41
|
+
@echo "Bash completion for $(SCRIPTS) removed. Edit your .bashrc and remove the source lines."
|
|
42
|
+
|
|
43
|
+
clean: ## Remove local virtualenv, caches, tags, and egg-info
|
|
44
|
+
@rm -rf .venv
|
|
45
|
+
@rm -rf dist build
|
|
46
|
+
@rm -f tags cscope.files cscope.out
|
|
47
|
+
@find src -maxdepth 1 -type d -name "*.egg-info" -exec rm -rf {} +
|
|
48
|
+
@find . -type d -name "__pycache__" -exec rm -r {} +
|
|
49
|
+
|
|
50
|
+
cscope: ## Build cscope and ctags indexes (optionally includes ../kiwi-scan)
|
|
51
|
+
find . \( -path ./.venv -o -path ./build -o -path ./dist \) -prune \
|
|
52
|
+
-o -name "*.py" -print > cscope.files
|
|
53
|
+
if [ -d ../kiwi-scan ]; then \
|
|
54
|
+
find ../kiwi-scan/ \
|
|
55
|
+
\( -path ../kiwi-scan/.venv \
|
|
56
|
+
-o -path ../kiwi-scan/build \
|
|
57
|
+
-o -path ../kiwi-scan/dist \) -prune \
|
|
58
|
+
-o -name "*.py" -print >> cscope.files; \
|
|
59
|
+
fi
|
|
60
|
+
cscope -b -i cscope.files
|
|
61
|
+
ctags -R --languages=Python .
|
|
62
|
+
|
|
63
|
+
tag: ## Create a version+timestamp git tag from COMMIT
|
|
64
|
+
@base="$(VERSION)"; \
|
|
65
|
+
commit="$(COMMIT)"; \
|
|
66
|
+
if git rev-parse "$$base" >/dev/null 2>&1; then \
|
|
67
|
+
echo "ERROR: release tag $$base already exists"; \
|
|
68
|
+
exit 1; \
|
|
69
|
+
fi; \
|
|
70
|
+
ts=$$(git show -s --format=%cd --date=format:%Y%m%d%H%M%S "$$commit"); \
|
|
71
|
+
tag="$$base+$$ts"; \
|
|
72
|
+
if git rev-parse "$$tag" >/dev/null 2>&1; then \
|
|
73
|
+
echo "ERROR: tag $$tag already exists"; \
|
|
74
|
+
exit 1; \
|
|
75
|
+
fi; \
|
|
76
|
+
echo "Creating tag $$tag"; \
|
|
77
|
+
git tag -a "$$tag" "$$commit" -m "Release $$tag"
|
|
78
|
+
|
|
79
|
+
build: ## Build sdist and wheel into dist/
|
|
80
|
+
@$(WITH_VENV); \
|
|
81
|
+
python -m build
|
|
82
|
+
|
|
83
|
+
lint: ## Run pylint, ruff, and pyright (uses mkvenv.sh when needed)
|
|
84
|
+
@echo
|
|
85
|
+
@echo '========================================================================'
|
|
86
|
+
@echo ' PYLINT'
|
|
87
|
+
@echo '========================================================================'
|
|
88
|
+
@$(WITH_VENV); \
|
|
89
|
+
PYTHONPATH=src pylint src/seq_quince
|
|
90
|
+
@echo '========================================================================'
|
|
91
|
+
@echo
|
|
92
|
+
@echo '========================================================================'
|
|
93
|
+
@echo ' RUFF'
|
|
94
|
+
@echo '========================================================================'
|
|
95
|
+
@$(WITH_VENV); \
|
|
96
|
+
ruff check src/seq_quince tests/
|
|
97
|
+
@echo '========================================================================'
|
|
98
|
+
@echo
|
|
99
|
+
@echo '========================================================================'
|
|
100
|
+
@echo ' PYRIGHT'
|
|
101
|
+
@echo '========================================================================'
|
|
102
|
+
@$(WITH_VENV); \
|
|
103
|
+
pyright src/seq_quince
|
|
104
|
+
@echo '========================================================================'
|
|
105
|
+
@echo
|
|
106
|
+
@echo 'Lint complete.'
|
|
107
|
+
|
|
108
|
+
test: ## Run the test suite (uses mkvenv.sh when needed)
|
|
109
|
+
@$(WITH_VENV); \
|
|
110
|
+
PYTHONPATH=src python -m pytest --cov=src/seq_quince --cov-report=term
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: seq-quince
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: An asynchronous, YAML-configured device scheduler for EPICS beamlines, built on kiwi-scan.
|
|
5
|
+
Author: Helmholtz-Zentrum Berlin
|
|
6
|
+
Project-URL: Homepage, https://github.com/hz-b/seq-quince
|
|
7
|
+
Project-URL: Repository, https://github.com/hz-b/seq-quince
|
|
8
|
+
Project-URL: Issues, https://github.com/hz-b/seq-quince/issues
|
|
9
|
+
Keywords: epics,scientific,instrumentation,beamline,control-system,scheduler,sequence,data-acquisition,scan,automation,yaml
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
+
Requires-Python: >=3.8
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: kiwi-scan>=0.6.1
|
|
31
|
+
Requires-Dist: PyYAML>=6.0
|
|
32
|
+
Requires-Dist: jinja2
|
|
33
|
+
Requires-Dist: pyepics>=3.5
|
|
34
|
+
Requires-Dist: ophyd
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
38
|
+
Requires-Dist: pyright; extra == "dev"
|
|
39
|
+
Requires-Dist: caproto[complete]; extra == "dev"
|
|
40
|
+
Requires-Dist: pylint; extra == "dev"
|
|
41
|
+
Requires-Dist: ruff; extra == "dev"
|
|
42
|
+
Requires-Dist: bandit; extra == "dev"
|
|
43
|
+
|
|
44
|
+
# seq-quince
|
|
45
|
+
|
|
46
|
+
`seq-quince`: An asynchronous, YAML-configured device scheduler for EPICS beamlines, built on [kiwi-scan](https://github.com/hz-b/kiwi-scan).
|
|
47
|
+
|
|
48
|
+
Devices (motors, actuators, hexapods, PV sequences, scans, wait conditions, …) are declared in a
|
|
49
|
+
`beamline.yaml`. Named **presets** map devices to target poses. The scheduler turns a preset into a sequence of move
|
|
50
|
+
steps, runs them serially or in parallel groups, and rolls completed steps back to their pre-run snapshot if a step fails.
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## Overview of Features
|
|
54
|
+
|
|
55
|
+
- **YAML + Jinja2 configuration** – `beamline.yaml` and `presets.yaml` are rendered as Jinja2 templates; both `${TOKEN}` and
|
|
56
|
+
`{{ TOKEN }}` placeholders are supported, as well as `{% include %}` from sibling/parent directories.
|
|
57
|
+
- **Device abstraction** – every device implements `DeviceABC` (`process`, `snapshot`, `busy`, `stop`, optional capabilities).
|
|
58
|
+
- **Device factory** – devices are created from config by their `type:` key; custom types can be registered at runtime.
|
|
59
|
+
- **Custom device extensions** — Load external device modules and packages via SEQ_QUINCE_DEVICE_PATH, with explicit registration through DEVICE_CLASSES.
|
|
60
|
+
- **Sequences** – presets are converted into `MoveStep` lists with `serial` or `parallel` grouping and barriers.
|
|
61
|
+
- **Async scheduler** – concurrent execution per group, optional per-step timeouts, best-effort rollback on failure.
|
|
62
|
+
- **Snapshots** – current device read-backs can be saved as a new preset (`presets.yaml`).
|
|
63
|
+
- **kiwi-scan integration** – `ScanDevice` runs any kiwi-scan scan type, `ActuatorDevice`/`HexapodDevice` use kiwi-scan actuators.
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install seq-quince
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
For development, clone the repository and install it in editable mode:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install -e .
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
## Command line tools
|
|
79
|
+
|
|
80
|
+
| Command | Purpose |
|
|
81
|
+
|---------|---------|
|
|
82
|
+
| `scheduler_runner` | Run a preset (`--config`, `--preset`, `--loop`, `--delay`, `--snapshot`, `--replace`) |
|
|
83
|
+
| `device_runner` | Run one device from a preset; see [device runner](https://github.com/hz-b/seq-quince/blob/master/docs/device_runner.md). |
|
|
84
|
+
|
|
85
|
+
Example:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
scheduler_runner --config u171dcm1 --preset branch --replace BL=U171
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Configuration
|
|
92
|
+
|
|
93
|
+
See [docs/devices.md](https://github.com/hz-b/seq-quince/blob/master/docs/devices.md) and [docs/devices](https://github.com/hz-b/seq-quince/tree/master/docs/devices) for configuration details.
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
# beamline.yaml
|
|
97
|
+
devices:
|
|
98
|
+
- name: simulation
|
|
99
|
+
type: SimDevice
|
|
100
|
+
delay: 0.1
|
|
101
|
+
- name: mono_energy
|
|
102
|
+
type: ActuatorDevice
|
|
103
|
+
config: { ... } # kiwi-scan ActuatorConfig
|
|
104
|
+
|
|
105
|
+
presets:
|
|
106
|
+
branch:
|
|
107
|
+
simulation: { position: 10.0 }
|
|
108
|
+
mono_energy: { position: 400.0 }
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Presets from `presets.yaml` (written by `--snapshot`) override presets of the same name in `beamline.yaml`.
|
|
112
|
+
Unknown preset names raise `ValueError`; an explicitly configured empty preset
|
|
113
|
+
(`empty: {}`) remains valid. Device names must be unique: duplicates are rejected
|
|
114
|
+
before any devices are constructed.
|
|
115
|
+
|
|
116
|
+
After template rendering, YAML is parsed with `yaml.safe_load()`. Python-specific
|
|
117
|
+
YAML tags are rejected. Normal loading does not print the rendered configuration.
|
|
118
|
+
|
|
119
|
+
Environment variables used by the code:
|
|
120
|
+
|
|
121
|
+
| Variable | Used for |
|
|
122
|
+
|------------------------|-----------------------------------------------------------------|
|
|
123
|
+
| `SCHEDULER_CONFIG_DIR` | Direct configuration directory; overrides beamline-name lookup. |
|
|
124
|
+
| `SEQ_QUINCE_CONFIG_DIR` | Existing base directory; looks in `<dir>/scheduler_config/beamlines/<config>` when no direct override is set. |
|
|
125
|
+
| `SCAN_CONFIG_DIR` | kiwi-scan config directory for `ScanDevice` |
|
|
126
|
+
| `SCAN_DATA_DIR` | Output directory for `ScanDevice` scans |
|
|
127
|
+
|
|
128
|
+
See [docs/devices.md](https://github.com/hz-b/seq-quince/blob/master/docs/devices.md) for the built-in device types and their configuration keys.
|
|
129
|
+
|
|
130
|
+
## Development
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
make help # list targets
|
|
134
|
+
make lint # pylint, ruff, pyright
|
|
135
|
+
make test # pytest
|
|
136
|
+
make build # sdist + wheel
|
|
137
|
+
make install_completion # install bash completion scripts
|
|
138
|
+
make uninstall_completion # uninstall bash completion scripts
|
|
139
|
+
make clean # Remove local virtualenv and python generated files
|
|
140
|
+
make cscope # Build cscope and ctags indexes
|
|
141
|
+
make tag # create git tag
|
|
142
|
+
```
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT – see [LICENSE](https://github.com/hz-b/seq-quince/blob/master/LICENSE).
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# seq-quince
|
|
2
|
+
|
|
3
|
+
`seq-quince`: An asynchronous, YAML-configured device scheduler for EPICS beamlines, built on [kiwi-scan](https://github.com/hz-b/kiwi-scan).
|
|
4
|
+
|
|
5
|
+
Devices (motors, actuators, hexapods, PV sequences, scans, wait conditions, …) are declared in a
|
|
6
|
+
`beamline.yaml`. Named **presets** map devices to target poses. The scheduler turns a preset into a sequence of move
|
|
7
|
+
steps, runs them serially or in parallel groups, and rolls completed steps back to their pre-run snapshot if a step fails.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Overview of Features
|
|
11
|
+
|
|
12
|
+
- **YAML + Jinja2 configuration** – `beamline.yaml` and `presets.yaml` are rendered as Jinja2 templates; both `${TOKEN}` and
|
|
13
|
+
`{{ TOKEN }}` placeholders are supported, as well as `{% include %}` from sibling/parent directories.
|
|
14
|
+
- **Device abstraction** – every device implements `DeviceABC` (`process`, `snapshot`, `busy`, `stop`, optional capabilities).
|
|
15
|
+
- **Device factory** – devices are created from config by their `type:` key; custom types can be registered at runtime.
|
|
16
|
+
- **Custom device extensions** — Load external device modules and packages via SEQ_QUINCE_DEVICE_PATH, with explicit registration through DEVICE_CLASSES.
|
|
17
|
+
- **Sequences** – presets are converted into `MoveStep` lists with `serial` or `parallel` grouping and barriers.
|
|
18
|
+
- **Async scheduler** – concurrent execution per group, optional per-step timeouts, best-effort rollback on failure.
|
|
19
|
+
- **Snapshots** – current device read-backs can be saved as a new preset (`presets.yaml`).
|
|
20
|
+
- **kiwi-scan integration** – `ScanDevice` runs any kiwi-scan scan type, `ActuatorDevice`/`HexapodDevice` use kiwi-scan actuators.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install seq-quince
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
For development, clone the repository and install it in editable mode:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install -e .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Command line tools
|
|
36
|
+
|
|
37
|
+
| Command | Purpose |
|
|
38
|
+
|---------|---------|
|
|
39
|
+
| `scheduler_runner` | Run a preset (`--config`, `--preset`, `--loop`, `--delay`, `--snapshot`, `--replace`) |
|
|
40
|
+
| `device_runner` | Run one device from a preset; see [device runner](https://github.com/hz-b/seq-quince/blob/master/docs/device_runner.md). |
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
scheduler_runner --config u171dcm1 --preset branch --replace BL=U171
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
See [docs/devices.md](https://github.com/hz-b/seq-quince/blob/master/docs/devices.md) and [docs/devices](https://github.com/hz-b/seq-quince/tree/master/docs/devices) for configuration details.
|
|
51
|
+
|
|
52
|
+
```yaml
|
|
53
|
+
# beamline.yaml
|
|
54
|
+
devices:
|
|
55
|
+
- name: simulation
|
|
56
|
+
type: SimDevice
|
|
57
|
+
delay: 0.1
|
|
58
|
+
- name: mono_energy
|
|
59
|
+
type: ActuatorDevice
|
|
60
|
+
config: { ... } # kiwi-scan ActuatorConfig
|
|
61
|
+
|
|
62
|
+
presets:
|
|
63
|
+
branch:
|
|
64
|
+
simulation: { position: 10.0 }
|
|
65
|
+
mono_energy: { position: 400.0 }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Presets from `presets.yaml` (written by `--snapshot`) override presets of the same name in `beamline.yaml`.
|
|
69
|
+
Unknown preset names raise `ValueError`; an explicitly configured empty preset
|
|
70
|
+
(`empty: {}`) remains valid. Device names must be unique: duplicates are rejected
|
|
71
|
+
before any devices are constructed.
|
|
72
|
+
|
|
73
|
+
After template rendering, YAML is parsed with `yaml.safe_load()`. Python-specific
|
|
74
|
+
YAML tags are rejected. Normal loading does not print the rendered configuration.
|
|
75
|
+
|
|
76
|
+
Environment variables used by the code:
|
|
77
|
+
|
|
78
|
+
| Variable | Used for |
|
|
79
|
+
|------------------------|-----------------------------------------------------------------|
|
|
80
|
+
| `SCHEDULER_CONFIG_DIR` | Direct configuration directory; overrides beamline-name lookup. |
|
|
81
|
+
| `SEQ_QUINCE_CONFIG_DIR` | Existing base directory; looks in `<dir>/scheduler_config/beamlines/<config>` when no direct override is set. |
|
|
82
|
+
| `SCAN_CONFIG_DIR` | kiwi-scan config directory for `ScanDevice` |
|
|
83
|
+
| `SCAN_DATA_DIR` | Output directory for `ScanDevice` scans |
|
|
84
|
+
|
|
85
|
+
See [docs/devices.md](https://github.com/hz-b/seq-quince/blob/master/docs/devices.md) for the built-in device types and their configuration keys.
|
|
86
|
+
|
|
87
|
+
## Development
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
make help # list targets
|
|
91
|
+
make lint # pylint, ruff, pyright
|
|
92
|
+
make test # pytest
|
|
93
|
+
make build # sdist + wheel
|
|
94
|
+
make install_completion # install bash completion scripts
|
|
95
|
+
make uninstall_completion # uninstall bash completion scripts
|
|
96
|
+
make clean # Remove local virtualenv and python generated files
|
|
97
|
+
make cscope # Build cscope and ctags indexes
|
|
98
|
+
make tag # create git tag
|
|
99
|
+
```
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT – see [LICENSE](https://github.com/hz-b/seq-quince/blob/master/LICENSE).
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Bash completion for device_runner.
|
|
2
|
+
_seq_quince_device_runner()
|
|
3
|
+
{
|
|
4
|
+
local current="${COMP_WORDS[COMP_CWORD]}"
|
|
5
|
+
local previous="${COMP_WORDS[COMP_CWORD-1]}"
|
|
6
|
+
COMPREPLY=()
|
|
7
|
+
case "$previous" in
|
|
8
|
+
--log-level)
|
|
9
|
+
mapfile -t COMPREPLY < <(compgen -W '0 1 2 3 4 5' -- "$current")
|
|
10
|
+
;;
|
|
11
|
+
--config|--device|--preset|--replace)
|
|
12
|
+
;;
|
|
13
|
+
*)
|
|
14
|
+
mapfile -t COMPREPLY < <(compgen -W \
|
|
15
|
+
'--config --device --preset --replace --log-level --help' -- "$current")
|
|
16
|
+
;;
|
|
17
|
+
esac
|
|
18
|
+
}
|
|
19
|
+
complete -F _seq_quince_device_runner device_runner
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
_scheduler_runner_complete()
|
|
2
|
+
{
|
|
3
|
+
local cur prev opts loop_values preset_values expecting word i
|
|
4
|
+
COMPREPLY=()
|
|
5
|
+
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
6
|
+
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
7
|
+
|
|
8
|
+
opts="--help -h --replace --config --preset --snapshot --loop --delay --log-level"
|
|
9
|
+
loop_values="infinite inf forever 1 2 5 10 100"
|
|
10
|
+
preset_values="default test prod dev"
|
|
11
|
+
|
|
12
|
+
case "$prev" in
|
|
13
|
+
--config)
|
|
14
|
+
# Complete files for config
|
|
15
|
+
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
16
|
+
return 0
|
|
17
|
+
;;
|
|
18
|
+
--preset)
|
|
19
|
+
# Optionally, offer static completions or load dynamically
|
|
20
|
+
COMPREPLY=( $(compgen -W "${preset_values}" -- "$cur") )
|
|
21
|
+
return 0
|
|
22
|
+
;;
|
|
23
|
+
--delay)
|
|
24
|
+
# Numeric value expected
|
|
25
|
+
COMPREPLY=()
|
|
26
|
+
return 0
|
|
27
|
+
;;
|
|
28
|
+
--loop)
|
|
29
|
+
# Optional argument: either suggest loop values or allow another option
|
|
30
|
+
if [[ "$cur" == -* ]]; then
|
|
31
|
+
COMPREPLY=( $(compgen -W "${opts}" -- "$cur") )
|
|
32
|
+
else
|
|
33
|
+
COMPREPLY=( $(compgen -W "${loop_values}" -- "$cur") )
|
|
34
|
+
fi
|
|
35
|
+
return 0
|
|
36
|
+
;;
|
|
37
|
+
--replace)
|
|
38
|
+
# User is likely entering KEY=VALUE, but allow switching to another option
|
|
39
|
+
if [[ "$cur" == -* ]]; then
|
|
40
|
+
COMPREPLY=( $(compgen -W "${opts}" -- "$cur") )
|
|
41
|
+
else
|
|
42
|
+
COMPREPLY=()
|
|
43
|
+
fi
|
|
44
|
+
return 0
|
|
45
|
+
;;
|
|
46
|
+
esac
|
|
47
|
+
|
|
48
|
+
# Track whether we are currently collecting values for an option.
|
|
49
|
+
expecting=""
|
|
50
|
+
for ((i=1; i<COMP_CWORD; i++)); do
|
|
51
|
+
word="${COMP_WORDS[i]}"
|
|
52
|
+
|
|
53
|
+
if [[ -n "$expecting" ]]; then
|
|
54
|
+
case "$expecting" in
|
|
55
|
+
replace)
|
|
56
|
+
if [[ "$word" == -* ]]; then
|
|
57
|
+
expecting=""
|
|
58
|
+
else
|
|
59
|
+
continue
|
|
60
|
+
fi
|
|
61
|
+
;;
|
|
62
|
+
loop)
|
|
63
|
+
if [[ "$word" == -* ]]; then
|
|
64
|
+
expecting=""
|
|
65
|
+
else
|
|
66
|
+
expecting=""
|
|
67
|
+
continue
|
|
68
|
+
fi
|
|
69
|
+
;;
|
|
70
|
+
config|preset|delay)
|
|
71
|
+
expecting=""
|
|
72
|
+
continue
|
|
73
|
+
;;
|
|
74
|
+
esac
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
case "$word" in
|
|
78
|
+
--replace)
|
|
79
|
+
expecting="replace"
|
|
80
|
+
;;
|
|
81
|
+
--config)
|
|
82
|
+
expecting="config"
|
|
83
|
+
;;
|
|
84
|
+
--preset)
|
|
85
|
+
expecting="preset"
|
|
86
|
+
;;
|
|
87
|
+
--delay)
|
|
88
|
+
expecting="delay"
|
|
89
|
+
;;
|
|
90
|
+
--loop)
|
|
91
|
+
expecting="loop"
|
|
92
|
+
;;
|
|
93
|
+
esac
|
|
94
|
+
done
|
|
95
|
+
|
|
96
|
+
if [[ "$expecting" == "replace" && "$cur" != -* ]]; then
|
|
97
|
+
COMPREPLY=()
|
|
98
|
+
return 0
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# If current word starts with '-', complete options
|
|
102
|
+
if [[ "$cur" == -* ]]; then
|
|
103
|
+
COMPREPLY=( $(compgen -W "${opts}" -- "$cur") )
|
|
104
|
+
return 0
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# Default to completing files for last positional argument (if any)
|
|
108
|
+
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
complete -F _scheduler_runner_complete scheduler_runner
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Run one device preset
|
|
2
|
+
|
|
3
|
+
Run only one configured device's entry from a named preset:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
device_runner --config my_beamline --device mono --preset alignment
|
|
7
|
+
device_runner --config my_beamline --device mono --preset alignment --replace IOC=TEST
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
The configuration name and directory resolution match `scheduler_runner`:
|
|
11
|
+
`SCHEDULER_CONFIG_DIR` overrides the directory, otherwise the usual
|
|
12
|
+
`SEQ_QUINCE_CONFIG_DIR` or packaged beamline directory is used. The files are
|
|
13
|
+
`beamline.yaml` and optional `presets.yaml`; saved presets retain their normal
|
|
14
|
+
precedence. `--config` takes the beamline name, as in the existing CLI.
|
|
15
|
+
|
|
16
|
+
For example, given this `beamline.yaml`:
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
devices:
|
|
20
|
+
- name: mono
|
|
21
|
+
type: SimDevice
|
|
22
|
+
initial_pose: {position: 1.0}
|
|
23
|
+
delay: 0
|
|
24
|
+
- name: slit
|
|
25
|
+
type: SimDevice
|
|
26
|
+
initial_pose: {position: 0.0}
|
|
27
|
+
delay: 0
|
|
28
|
+
presets:
|
|
29
|
+
alignment:
|
|
30
|
+
mono: {position: 5.0}
|
|
31
|
+
slit: {position: 2.0}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The example command constructs `mono` and runs it with `{position: 5.0}`.
|
|
35
|
+
It does not construct `slit`. Selecting a composite device, such as
|
|
36
|
+
`SequenceDevice`, still executes that device's configured internal operations.
|
|
37
|
+
Empty entries (`mono: {}`) are accepted for devices that use configured defaults.
|
|
38
|
+
|
|
39
|
+
The selection is validated before the factory constructs the device. An
|
|
40
|
+
unknown preset or device, a duplicate device name, a missing device entry,
|
|
41
|
+
or an entry that is not a mapping causes an error. Selected config and pose
|
|
42
|
+
data are copied before use, preserving the loaded manager's configuration.
|
|
43
|
+
|
|
44
|
+
The existing scheduler executes one step and applies the device's scheduler
|
|
45
|
+
timeout. Device implementations retain their normal processing and error
|
|
46
|
+
cleanup. The scheduler rolls back completed steps; a single failing step
|
|
47
|
+
has no completed step to roll back. Cancelling the async operation attempts
|
|
48
|
+
`device.stop()` before propagating cancellation. A stop failure is logged
|
|
49
|
+
without hiding cancellation. Ctrl-C uses this cancellation path during
|
|
50
|
+
execution and exits with status 130; a repeated interrupt can abort cleanup.
|
|
51
|
+
|
|
52
|
+
| Option | Meaning |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| `--config NAME` | Required beamline configuration name |
|
|
55
|
+
| `--device NAME` | Required configured device name |
|
|
56
|
+
| `--preset NAME` | Required named preset containing that device |
|
|
57
|
+
| `--replace KEY=VALUE ...` | Standard configuration replacements |
|
|
58
|
+
| `--log-level 0-5` | Same logging control as `scheduler_runner` |
|
|
59
|
+
|
|
60
|
+
Exit codes: 0 for success, 1 for configuration or execution failure, 2 for
|
|
61
|
+
argument errors, and 130 for interruption. Execution runs once.
|
|
62
|
+
|
|
63
|
+
## Python use
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from seq_quince.runner import run_device
|
|
67
|
+
|
|
68
|
+
run_device("my_beamline", "mono", "alignment", replacements={"IOC": "TEST"})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
For an application that already owns an event loop:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from seq_quince.runner import create_config_manager, run_device_preset
|
|
75
|
+
|
|
76
|
+
async def align_mono():
|
|
77
|
+
manager = create_config_manager("my_beamline", {"IOC": "TEST"})
|
|
78
|
+
await run_device_preset(manager, "mono", "alignment")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`run_device()` owns an event loop; use the async helper inside a running loop.
|
|
82
|
+
Device construction happens inside the execution loop, including on Python 3.8.
|
|
83
|
+
`create_device_step()` is also available for applications building their own
|
|
84
|
+
scheduler steps; call it inside the loop used to execute the device.
|