labcode 0.8.0__tar.gz → 0.9.0__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.
- {labcode-0.8.0 → labcode-0.9.0}/.github/workflows/ci.yml +15 -0
- labcode-0.9.0/CHANGELOG.md +63 -0
- {labcode-0.8.0 → labcode-0.9.0}/PKG-INFO +63 -18
- {labcode-0.8.0 → labcode-0.9.0}/README.md +55 -16
- {labcode-0.8.0 → labcode-0.9.0/docs}/SPECIFICATIONS.md +161 -28
- {labcode-0.8.0 → labcode-0.9.0}/examples/README.md +62 -12
- labcode-0.9.0/examples/lads_plate_cycle.env.yaml +288 -0
- labcode-0.9.0/examples/lads_seal.env.yaml +100 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/preflight_sila2_env.py +24 -9
- {labcode-0.8.0 → labcode-0.9.0}/examples/render_sila2_plate_cycle.py +3 -2
- labcode-0.9.0/examples/run_all_lads_examples.py +93 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/run_all_sila2_examples.py +1 -1
- {labcode-0.8.0 → labcode-0.9.0}/examples/run_sila2_plate_cycle.py +9 -5
- {labcode-0.8.0 → labcode-0.9.0}/examples/run_sila2_plate_cycle_no_atc.py +5 -5
- {labcode-0.8.0 → labcode-0.9.0}/examples/run_sila2_seal.py +4 -3
- {labcode-0.8.0 → labcode-0.9.0}/examples/sila2_plate_cycle.boundary.yaml +1 -1
- {labcode-0.8.0 → labcode-0.9.0}/examples/sila2_plate_cycle.wrapped.env.yaml +7 -7
- {labcode-0.8.0 → labcode-0.9.0}/examples/sila2_plate_cycle_no_atc.boundary.yaml +1 -1
- {labcode-0.8.0 → labcode-0.9.0}/examples/sila2_plate_cycle_no_atc.wrapped.env.yaml +7 -7
- {labcode-0.8.0 → labcode-0.9.0}/examples/sila2_seal.boundary.yaml +1 -1
- {labcode-0.8.0 → labcode-0.9.0}/examples/sila2_seal.env.yaml +2 -2
- {labcode-0.8.0 → labcode-0.9.0}/examples/sila2_seal.wrapped.env.yaml +4 -4
- {labcode-0.8.0 → labcode-0.9.0}/labcode/backend.py +30 -19
- {labcode-0.8.0 → labcode-0.9.0}/labcode/cli.py +16 -1
- labcode-0.9.0/labcode/connections.py +257 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode/dialect.py +80 -34
- {labcode-0.8.0 → labcode-0.9.0}/labcode/extension.py +44 -7
- labcode-0.9.0/labcode/lads.py +181 -0
- labcode-0.9.0/labcode/lads_commands.py +167 -0
- labcode-0.9.0/labcode/lads_unit.py +331 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode/probe.py +4 -4
- labcode-0.9.0/labcode/sila2.py +154 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode/sila2_commands.py +1 -1
- {labcode-0.8.0 → labcode-0.9.0}/labcode.egg-info/PKG-INFO +63 -18
- {labcode-0.8.0 → labcode-0.9.0}/labcode.egg-info/SOURCES.txt +11 -1
- {labcode-0.8.0 → labcode-0.9.0}/labcode.egg-info/requires.txt +9 -1
- {labcode-0.8.0 → labcode-0.9.0}/labcode.egg-info/scm_file_list.json +11 -1
- labcode-0.9.0/labcode.egg-info/scm_version.json +8 -0
- {labcode-0.8.0 → labcode-0.9.0}/pyproject.toml +25 -8
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_backend.py +74 -1
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_cli.py +16 -1
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_dialect.py +105 -3
- labcode-0.9.0/tests/test_lads.py +184 -0
- labcode-0.9.0/tests/test_lads_unit.py +396 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_sila2.py +2 -2
- labcode-0.8.0/labcode/sila2.py +0 -280
- labcode-0.8.0/labcode.egg-info/scm_version.json +0 -8
- {labcode-0.8.0 → labcode-0.9.0}/.github/workflows/publish.yml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/.gitignore +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/LICENSE +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/MANIFEST.in +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/outputs/plate_line.boundary.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/outputs/plate_line.observation.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/outputs/plate_line.plan.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/outputs/plate_line.svg +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/outputs/shared_bench.boundary.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/outputs/shared_bench.observation.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/outputs/shared_bench.plan.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/outputs/shared_bench.svg +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/outputs/sila2_plate_cycle.boundary.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/outputs/sila2_plate_cycle.observation.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/outputs/sila2_plate_cycle.plan.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/outputs/sila2_plate_cycle.svg +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/plate_line.boundary.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/plate_line.env.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/plate_line.workflow.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/render_plate_line.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/render_shared_bench.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/shared_bench.afternoon.boundary.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/shared_bench.env.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/shared_bench.morning.boundary.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/shared_bench.run.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/shared_bench.workflow.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/sila2_plate_cycle.workflow.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/sila2_plate_cycle_no_atc.workflow.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/examples/sila2_seal.workflow.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode/__init__.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode/__main__.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode/_child.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode/idgen.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode/objectid.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode/otel.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode/otel_sila2.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode/py.typed +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode/record.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode/run_cli.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode/runner.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode/sila2_instrument.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode.egg-info/dependency_links.txt +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode.egg-info/entry_points.txt +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/labcode.egg-info/top_level.txt +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/setup.cfg +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/fixtures/device_script.env.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/fixtures/device_script.workflow.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/fixtures/joint.run.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/fixtures/replenishment.env.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/fixtures/reroute_device.env.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/fixtures/reroute_transporter.env.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/fixtures/transport.env.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/fixtures/transport.workflow.yaml +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_joint.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_objectid.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_otel.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_otel_child.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_otel_grpc.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_otel_sila2.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_probe.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_record.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_recording.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_run_cli.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_sila2_commands.py +0 -0
- {labcode-0.8.0 → labcode-0.9.0}/tests/test_sila2_instrument.py +0 -0
|
@@ -22,6 +22,21 @@ jobs:
|
|
|
22
22
|
- run: pip install -e ".[test]"
|
|
23
23
|
- run: pytest
|
|
24
24
|
|
|
25
|
+
test-export:
|
|
26
|
+
# `lc export` is the optional extra `labcode[export]`, so the matrix above runs
|
|
27
|
+
# without it and tests the "not installed" answer. This installs it the way a
|
|
28
|
+
# user would -- through the extra -- and tests that `lc export` reaches it.
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v7
|
|
32
|
+
- uses: actions/setup-python@v7
|
|
33
|
+
with:
|
|
34
|
+
python-version: "3.12"
|
|
35
|
+
cache: pip
|
|
36
|
+
- run: pip install -e ".[test,export]"
|
|
37
|
+
- run: python -c "import ofplang.export"
|
|
38
|
+
- run: pytest tests/test_cli.py
|
|
39
|
+
|
|
25
40
|
lint:
|
|
26
41
|
runs-on: ubuntu-latest
|
|
27
42
|
steps:
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
What changed in each release of labcode, for someone deciding whether to upgrade. The
|
|
4
|
+
specification the section numbers refer to is [`docs/SPECIFICATIONS.md`](docs/SPECIFICATIONS.md).
|
|
5
|
+
|
|
6
|
+
Releases before 0.9.0 are described in the messages of their `Release vX.Y.Z` commits
|
|
7
|
+
(`git log --grep '^Release v'`).
|
|
8
|
+
|
|
9
|
+
## 0.9.0 — 2026-09-28
|
|
10
|
+
|
|
11
|
+
labcode can now drive a lab over **LADS OPC UA** as well as SiLA2, and gains `lc export`.
|
|
12
|
+
Both are optional extras; nothing changes for an environment that uses neither.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`flavor: lads`** — a script that is the commands alone, handed its LADS OPC UA clients
|
|
17
|
+
as a `sila2` script is handed SiLA2 ones (§1.10).
|
|
18
|
+
- A connection may say `kind: lads`, and may name the `device` and functional `unit` on the
|
|
19
|
+
server; left out, each is the only one there is (§1.5).
|
|
20
|
+
- The script sees `lads_clients` / `lads_client`: one functional unit per machine, with the
|
|
21
|
+
program runs, state-machine methods, cover movements and set-points LADS defines, each
|
|
22
|
+
waiting for what it starts, and `.raw` for the asyncua client itself.
|
|
23
|
+
- `labcode.lads_commands` holds the waits: a program run to its result, a state machine to
|
|
24
|
+
settle (§1.10.1). A timeout fails the operation and aborts nothing, as SiLA2's does.
|
|
25
|
+
- A script speaks one protocol. An operation may hold machines of both kinds, but a script
|
|
26
|
+
commands only its own flavor's; reaching for the other kind says why
|
|
27
|
+
(`lads_other_protocol` / `sila2_other_protocol`).
|
|
28
|
+
- The client library is the `lads` extra (`asyncua`). labcode imports it only when a `lads`
|
|
29
|
+
script connects.
|
|
30
|
+
- **`lc export`** — forwards to [ofplang-export](https://github.com/ofplang/export)'s CLI
|
|
31
|
+
unchanged, as `lc validate` and `lc schedule` do. `lc export view plan.yaml -o plan.html`
|
|
32
|
+
writes the workflow and its plan as one self-contained HTML file. It is the `export` extra;
|
|
33
|
+
without it `lc export` names the extra and exits 2.
|
|
34
|
+
- **LADS examples** — `lads_seal.env.yaml` and `lads_plate_cycle.env.yaml` run the
|
|
35
|
+
`sila2_seal` and `sila2_plate_cycle` workflows unchanged, with every machine reached over
|
|
36
|
+
LADS. `run_all_lads_examples.py` runs both against the reference lab,
|
|
37
|
+
[ofplang/mocklab](https://github.com/ofplang/mocklab).
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- `SPECIFICATIONS.md` moved to `docs/SPECIFICATIONS.md`.
|
|
42
|
+
- The `ofplang-schedule` cap is `<0.13`, admitting schedule 0.12. The floor is unchanged.
|
|
43
|
+
- The front door checks a connecting script's connection **per kind**. A `sila2` or `lads`
|
|
44
|
+
script needs a machine of its own kind. An endpoint of the other kind draws a warning, as
|
|
45
|
+
an endpoint with no connection does. Messages that named only `sila2` now name the flavor.
|
|
46
|
+
- What a connecting flavor needs (planning the clients, holding them for one operation,
|
|
47
|
+
generating the wrapper) moved from `labcode.sila2` into `labcode.connections`. The `sila2`
|
|
48
|
+
flavor's behaviour, messages and reason codes are unchanged.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- `examples/preflight_sila2_env.py` treated every declared connection as SiLA2. An
|
|
53
|
+
environment with a LADS machine would have reported it as a failing SiLA2 server. It now
|
|
54
|
+
checks SiLA2 machines only and lists the others as not checked.
|
|
55
|
+
|
|
56
|
+
### Documentation
|
|
57
|
+
|
|
58
|
+
- The reference lab is named by its current name, ofplang/mocklab (formerly
|
|
59
|
+
ofplang-sila2-backend), and the examples record the commit they were last verified against.
|
|
60
|
+
- Section references written before probing took §1.6 pointed at the wrong sections and are
|
|
61
|
+
corrected. The `labcode.sila2_commands` section is numbered §1.7.1.
|
|
62
|
+
- The README lists the optional extras, lists every example, and links by absolute URL, so the
|
|
63
|
+
links also work on PyPI.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: labcode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: labcode -- a dialect wrapper over the Object-Flow Programming Language toolchain
|
|
5
5
|
Author-email: Kazunari Kaizu <kwaizu@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -21,20 +21,26 @@ Requires-Python: >=3.10
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: ofplang-validate<0.3,>=0.2
|
|
24
|
-
Requires-Dist: ofplang-schedule<0.
|
|
24
|
+
Requires-Dist: ofplang-schedule<0.13,>=0.9
|
|
25
25
|
Requires-Dist: ofplang-run<0.13,>=0.11
|
|
26
26
|
Provides-Extra: test
|
|
27
27
|
Requires-Dist: pytest>=7.0; extra == "test"
|
|
28
28
|
Requires-Dist: opentelemetry-sdk>=1.20; extra == "test"
|
|
29
29
|
Requires-Dist: opentelemetry-instrumentation-grpc[instruments]>=0.41b0; extra == "test"
|
|
30
|
+
Requires-Dist: asyncua>=2.0; extra == "test"
|
|
30
31
|
Provides-Extra: dev
|
|
31
32
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
33
|
Requires-Dist: ruff>=0.16; extra == "dev"
|
|
33
34
|
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
34
35
|
Requires-Dist: opentelemetry-sdk>=1.20; extra == "dev"
|
|
35
36
|
Requires-Dist: opentelemetry-instrumentation-grpc[instruments]>=0.41b0; extra == "dev"
|
|
37
|
+
Requires-Dist: asyncua>=2.0; extra == "dev"
|
|
36
38
|
Provides-Extra: sila2
|
|
37
39
|
Requires-Dist: sila2>=0.14; extra == "sila2"
|
|
40
|
+
Provides-Extra: lads
|
|
41
|
+
Requires-Dist: asyncua>=2.0; extra == "lads"
|
|
42
|
+
Provides-Extra: export
|
|
43
|
+
Requires-Dist: ofplang-export<0.2,>=0.1; extra == "export"
|
|
38
44
|
Provides-Extra: otel
|
|
39
45
|
Requires-Dist: opentelemetry-api>=1.20; extra == "otel"
|
|
40
46
|
Requires-Dist: opentelemetry-sdk>=1.20; extra == "otel"
|
|
@@ -55,6 +61,7 @@ ofplang toolchain and exposes it under a single command:
|
|
|
55
61
|
lc validate ... # check a workflow is well-formed portable v0
|
|
56
62
|
lc schedule ... # compute a schedule for a workflow
|
|
57
63
|
lc run ... # execute a workflow on the labcode backend
|
|
64
|
+
lc export ... # write documents out for people to read (optional: labcode[export])
|
|
58
65
|
```
|
|
59
66
|
|
|
60
67
|
labcode is where a site-specific dialect and a custom runner (real lab hardware)
|
|
@@ -63,7 +70,7 @@ forward to the ofplang siblings unchanged; **`lc run` is the labcode dialect's o
|
|
|
63
70
|
runner**: it drives the workflow on the labcode backend, running each device
|
|
64
71
|
operation's script — supplied in the environment as an `x-labcode.script` extension
|
|
65
72
|
on a process mode — out-of-process on a wall clock, so a long-running real operation
|
|
66
|
-
never blocks the replan loop. See [`SPECIFICATIONS.md`](SPECIFICATIONS.md) for the
|
|
73
|
+
never blocks the replan loop. See [`docs/SPECIFICATIONS.md`](https://github.com/ofplang/labcode/blob/main/docs/SPECIFICATIONS.md) for the
|
|
67
74
|
`x-labcode` extension.
|
|
68
75
|
|
|
69
76
|
```yaml
|
|
@@ -94,7 +101,25 @@ sibling packages unchanged; `lc run` is this package's own runner, built on them
|
|
|
94
101
|
- [`ofplang-run`](https://github.com/ofplang/run) — the runner / simulator
|
|
95
102
|
|
|
96
103
|
The language is defined in the [ofplang/spec](https://github.com/ofplang/spec)
|
|
97
|
-
repository, and what labcode adds to it in [`SPECIFICATIONS.md`](SPECIFICATIONS.md).
|
|
104
|
+
repository, and what labcode adds to it in [`docs/SPECIFICATIONS.md`](https://github.com/ofplang/labcode/blob/main/docs/SPECIFICATIONS.md).
|
|
105
|
+
|
|
106
|
+
Talking to instruments, recording a run and exporting documents are optional, each an extra
|
|
107
|
+
installed into the interpreter that runs `lc` (labcode runs every script with that same
|
|
108
|
+
interpreter):
|
|
109
|
+
|
|
110
|
+
| extra | brings | needed for |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| `sila2` | the `sila2` client library | `flavor: sila2` scripts (§1.7) |
|
|
113
|
+
| `lads` | `asyncua`, an OPC UA client | `flavor: lads` scripts (§1.10) |
|
|
114
|
+
| `otel` | the OpenTelemetry SDK and exporter | `lc run --trace` |
|
|
115
|
+
| `export` | [`ofplang-export`](https://github.com/ofplang/export) | `lc export view` — the workflow and its plan as one self-contained HTML file |
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
pip install 'labcode[sila2,lads]'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Without an extra, labcode still runs; only what needs it — a script of that flavor,
|
|
122
|
+
`--trace`, or `lc export` — fails, with a message naming the extra to install.
|
|
98
123
|
|
|
99
124
|
What `lc run` brings of its own, beyond dispatching:
|
|
100
125
|
|
|
@@ -114,12 +139,18 @@ What `lc run` brings of its own, beyond dispatching:
|
|
|
114
139
|
- **object identity** — the reserved `_id` view key is declared on Object types and minted
|
|
115
140
|
per object, so a physical thing can be followed through a run (§4).
|
|
116
141
|
- **`flavor: sila2`** — a script that speaks SiLA2 gets its clients opened around it
|
|
117
|
-
(§1.7). The client library
|
|
118
|
-
|
|
142
|
+
(§1.7). The client library is the `sila2` extra.
|
|
143
|
+
- **`flavor: lads`** — the same for LADS OPC UA: a script is handed one LADS functional
|
|
144
|
+
unit per machine, already connected, with the program runs, state-machine methods and
|
|
145
|
+
cover movements LADS defines and the waits each needs (§1.10). A connection may name the
|
|
146
|
+
`device` and `unit` on the server, or leave them to be found. A script speaks one protocol;
|
|
147
|
+
an operation holding machines of both kinds commands only its own flavor's. The client
|
|
148
|
+
library is the `lads` extra.
|
|
119
149
|
- **recording a run** — with `--trace`, what the run did is recorded as OpenTelemetry
|
|
120
150
|
traces: one trace per run, a span per operation, and — measured inside the process that
|
|
121
151
|
issued them — a span per SiLA2 connection, per command, and per gRPC call each of those
|
|
122
|
-
made. Off by default; the extra
|
|
152
|
+
made. Off by default; it needs the `otel` extra. A `lads` operation is
|
|
153
|
+
recorded as its span alone for now.
|
|
123
154
|
|
|
124
155
|
## Usage
|
|
125
156
|
|
|
@@ -133,9 +164,11 @@ lc --version
|
|
|
133
164
|
|
|
134
165
|
`lc` can also be run as a module: `python -m labcode <command> ...`.
|
|
135
166
|
|
|
136
|
-
`lc validate` and `lc
|
|
137
|
-
options are documented in those repositories
|
|
138
|
-
|
|
167
|
+
`lc validate`, `lc schedule` and `lc export` are the ofplang siblings' own CLIs unchanged,
|
|
168
|
+
so their options are documented in those repositories — `lc export view plan.yaml -o
|
|
169
|
+
plan.html`, for one, writes the workflow and its plan as a single HTML file
|
|
170
|
+
([ofplang/export](https://github.com/ofplang/export); needs `labcode[export]`). `lc run` is
|
|
171
|
+
this package's own, and is described below.
|
|
139
172
|
|
|
140
173
|
### `lc run`
|
|
141
174
|
|
|
@@ -267,9 +300,10 @@ duration and nothing is commanded. That is a real thing to write (an operator to
|
|
|
267
300
|
stock up while the schedule waits for them) and an easy one to write by accident, so it is
|
|
268
301
|
warned about.
|
|
269
302
|
|
|
270
|
-
`flavor: sila2`
|
|
271
|
-
and which machine's clients a refill should receive — the replenisher's,
|
|
272
|
-
transport may ask for — is not settled. Use `
|
|
303
|
+
`flavor: sila2` and `flavor: lads` are **refused on a refill route** for now: such a script
|
|
304
|
+
is handed clients, and which machine's clients a refill should receive — the replenisher's,
|
|
305
|
+
or both ends' as a transport may ask for — is not settled. Use `raw` (the default), which may
|
|
306
|
+
connect for itself.
|
|
273
307
|
|
|
274
308
|
A refill holds the device it fills *and* the replenisher filling it, so it never overlaps
|
|
275
309
|
the work it feeds. It is recorded (`--trace`) as a `replenishment` span naming both
|
|
@@ -332,11 +366,22 @@ OpenTelemetry's own export timeout allows: labcode sets no timeout of its own, s
|
|
|
332
366
|
|
|
333
367
|
## Examples
|
|
334
368
|
|
|
335
|
-
[`examples/`](examples/README.md) holds
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
369
|
+
[`examples/`](https://github.com/ofplang/labcode/blob/main/examples/README.md) holds worked runs of two kinds.
|
|
370
|
+
|
|
371
|
+
- **Runnable with nothing but Python** — `plate_line`, an Object-bearing line driven entirely
|
|
372
|
+
by environment scripts, and `shared_bench`, two jobs sharing one laboratory (`--jobs`).
|
|
373
|
+
- **Against real instrument servers** — `sila2_seal` (one instrument), `sila2_plate_cycle` (a
|
|
374
|
+
circuit through four instruments and an arm) and `sila2_plate_cycle_no_atc` (the same
|
|
375
|
+
circuit on a bench without a thermal cycler), over SiLA2; and `lads_seal` and
|
|
376
|
+
`lads_plate_cycle`, the first two workflows unchanged with every machine reached over LADS
|
|
377
|
+
OPC UA instead. `sila2_seal` also walks through what a run does when a machine stops
|
|
378
|
+
answering — before an operation, and in the middle of one.
|
|
379
|
+
|
|
380
|
+
The servers they are verified against are the reference lab,
|
|
381
|
+
[ofplang/mocklab](https://github.com/ofplang/mocklab): mock instruments serving SiLA2 or LADS
|
|
382
|
+
OPC UA by Docker Compose profile, over one simulated world. It is a reference, not a
|
|
383
|
+
requirement — the environments speak plain SiLA2 and LADS, so a bench is a change of hosts and
|
|
384
|
+
ports. `run_all_sila2_examples.py` and `run_all_lads_examples.py` run each set as a check.
|
|
340
385
|
|
|
341
386
|
## License
|
|
342
387
|
|
|
@@ -11,6 +11,7 @@ ofplang toolchain and exposes it under a single command:
|
|
|
11
11
|
lc validate ... # check a workflow is well-formed portable v0
|
|
12
12
|
lc schedule ... # compute a schedule for a workflow
|
|
13
13
|
lc run ... # execute a workflow on the labcode backend
|
|
14
|
+
lc export ... # write documents out for people to read (optional: labcode[export])
|
|
14
15
|
```
|
|
15
16
|
|
|
16
17
|
labcode is where a site-specific dialect and a custom runner (real lab hardware)
|
|
@@ -19,7 +20,7 @@ forward to the ofplang siblings unchanged; **`lc run` is the labcode dialect's o
|
|
|
19
20
|
runner**: it drives the workflow on the labcode backend, running each device
|
|
20
21
|
operation's script — supplied in the environment as an `x-labcode.script` extension
|
|
21
22
|
on a process mode — out-of-process on a wall clock, so a long-running real operation
|
|
22
|
-
never blocks the replan loop. See [`SPECIFICATIONS.md`](SPECIFICATIONS.md) for the
|
|
23
|
+
never blocks the replan loop. See [`docs/SPECIFICATIONS.md`](https://github.com/ofplang/labcode/blob/main/docs/SPECIFICATIONS.md) for the
|
|
23
24
|
`x-labcode` extension.
|
|
24
25
|
|
|
25
26
|
```yaml
|
|
@@ -50,7 +51,25 @@ sibling packages unchanged; `lc run` is this package's own runner, built on them
|
|
|
50
51
|
- [`ofplang-run`](https://github.com/ofplang/run) — the runner / simulator
|
|
51
52
|
|
|
52
53
|
The language is defined in the [ofplang/spec](https://github.com/ofplang/spec)
|
|
53
|
-
repository, and what labcode adds to it in [`SPECIFICATIONS.md`](SPECIFICATIONS.md).
|
|
54
|
+
repository, and what labcode adds to it in [`docs/SPECIFICATIONS.md`](https://github.com/ofplang/labcode/blob/main/docs/SPECIFICATIONS.md).
|
|
55
|
+
|
|
56
|
+
Talking to instruments, recording a run and exporting documents are optional, each an extra
|
|
57
|
+
installed into the interpreter that runs `lc` (labcode runs every script with that same
|
|
58
|
+
interpreter):
|
|
59
|
+
|
|
60
|
+
| extra | brings | needed for |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `sila2` | the `sila2` client library | `flavor: sila2` scripts (§1.7) |
|
|
63
|
+
| `lads` | `asyncua`, an OPC UA client | `flavor: lads` scripts (§1.10) |
|
|
64
|
+
| `otel` | the OpenTelemetry SDK and exporter | `lc run --trace` |
|
|
65
|
+
| `export` | [`ofplang-export`](https://github.com/ofplang/export) | `lc export view` — the workflow and its plan as one self-contained HTML file |
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
pip install 'labcode[sila2,lads]'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Without an extra, labcode still runs; only what needs it — a script of that flavor,
|
|
72
|
+
`--trace`, or `lc export` — fails, with a message naming the extra to install.
|
|
54
73
|
|
|
55
74
|
What `lc run` brings of its own, beyond dispatching:
|
|
56
75
|
|
|
@@ -70,12 +89,18 @@ What `lc run` brings of its own, beyond dispatching:
|
|
|
70
89
|
- **object identity** — the reserved `_id` view key is declared on Object types and minted
|
|
71
90
|
per object, so a physical thing can be followed through a run (§4).
|
|
72
91
|
- **`flavor: sila2`** — a script that speaks SiLA2 gets its clients opened around it
|
|
73
|
-
(§1.7). The client library
|
|
74
|
-
|
|
92
|
+
(§1.7). The client library is the `sila2` extra.
|
|
93
|
+
- **`flavor: lads`** — the same for LADS OPC UA: a script is handed one LADS functional
|
|
94
|
+
unit per machine, already connected, with the program runs, state-machine methods and
|
|
95
|
+
cover movements LADS defines and the waits each needs (§1.10). A connection may name the
|
|
96
|
+
`device` and `unit` on the server, or leave them to be found. A script speaks one protocol;
|
|
97
|
+
an operation holding machines of both kinds commands only its own flavor's. The client
|
|
98
|
+
library is the `lads` extra.
|
|
75
99
|
- **recording a run** — with `--trace`, what the run did is recorded as OpenTelemetry
|
|
76
100
|
traces: one trace per run, a span per operation, and — measured inside the process that
|
|
77
101
|
issued them — a span per SiLA2 connection, per command, and per gRPC call each of those
|
|
78
|
-
made. Off by default; the extra
|
|
102
|
+
made. Off by default; it needs the `otel` extra. A `lads` operation is
|
|
103
|
+
recorded as its span alone for now.
|
|
79
104
|
|
|
80
105
|
## Usage
|
|
81
106
|
|
|
@@ -89,9 +114,11 @@ lc --version
|
|
|
89
114
|
|
|
90
115
|
`lc` can also be run as a module: `python -m labcode <command> ...`.
|
|
91
116
|
|
|
92
|
-
`lc validate` and `lc
|
|
93
|
-
options are documented in those repositories
|
|
94
|
-
|
|
117
|
+
`lc validate`, `lc schedule` and `lc export` are the ofplang siblings' own CLIs unchanged,
|
|
118
|
+
so their options are documented in those repositories — `lc export view plan.yaml -o
|
|
119
|
+
plan.html`, for one, writes the workflow and its plan as a single HTML file
|
|
120
|
+
([ofplang/export](https://github.com/ofplang/export); needs `labcode[export]`). `lc run` is
|
|
121
|
+
this package's own, and is described below.
|
|
95
122
|
|
|
96
123
|
### `lc run`
|
|
97
124
|
|
|
@@ -223,9 +250,10 @@ duration and nothing is commanded. That is a real thing to write (an operator to
|
|
|
223
250
|
stock up while the schedule waits for them) and an easy one to write by accident, so it is
|
|
224
251
|
warned about.
|
|
225
252
|
|
|
226
|
-
`flavor: sila2`
|
|
227
|
-
and which machine's clients a refill should receive — the replenisher's,
|
|
228
|
-
transport may ask for — is not settled. Use `
|
|
253
|
+
`flavor: sila2` and `flavor: lads` are **refused on a refill route** for now: such a script
|
|
254
|
+
is handed clients, and which machine's clients a refill should receive — the replenisher's,
|
|
255
|
+
or both ends' as a transport may ask for — is not settled. Use `raw` (the default), which may
|
|
256
|
+
connect for itself.
|
|
229
257
|
|
|
230
258
|
A refill holds the device it fills *and* the replenisher filling it, so it never overlaps
|
|
231
259
|
the work it feeds. It is recorded (`--trace`) as a `replenishment` span naming both
|
|
@@ -288,11 +316,22 @@ OpenTelemetry's own export timeout allows: labcode sets no timeout of its own, s
|
|
|
288
316
|
|
|
289
317
|
## Examples
|
|
290
318
|
|
|
291
|
-
[`examples/`](examples/README.md) holds
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
319
|
+
[`examples/`](https://github.com/ofplang/labcode/blob/main/examples/README.md) holds worked runs of two kinds.
|
|
320
|
+
|
|
321
|
+
- **Runnable with nothing but Python** — `plate_line`, an Object-bearing line driven entirely
|
|
322
|
+
by environment scripts, and `shared_bench`, two jobs sharing one laboratory (`--jobs`).
|
|
323
|
+
- **Against real instrument servers** — `sila2_seal` (one instrument), `sila2_plate_cycle` (a
|
|
324
|
+
circuit through four instruments and an arm) and `sila2_plate_cycle_no_atc` (the same
|
|
325
|
+
circuit on a bench without a thermal cycler), over SiLA2; and `lads_seal` and
|
|
326
|
+
`lads_plate_cycle`, the first two workflows unchanged with every machine reached over LADS
|
|
327
|
+
OPC UA instead. `sila2_seal` also walks through what a run does when a machine stops
|
|
328
|
+
answering — before an operation, and in the middle of one.
|
|
329
|
+
|
|
330
|
+
The servers they are verified against are the reference lab,
|
|
331
|
+
[ofplang/mocklab](https://github.com/ofplang/mocklab): mock instruments serving SiLA2 or LADS
|
|
332
|
+
OPC UA by Docker Compose profile, over one simulated world. It is a reference, not a
|
|
333
|
+
requirement — the environments speak plain SiLA2 and LADS, so a bench is a change of hosts and
|
|
334
|
+
ports. `run_all_sila2_examples.py` and `run_all_lads_examples.py` run each set as a check.
|
|
296
335
|
|
|
297
336
|
## License
|
|
298
337
|
|
|
@@ -16,7 +16,7 @@ The extension answers two different questions, in two kinds of place. On a **pro
|
|
|
16
16
|
mode**, a **transport route** and a **replenishment route** it says *what to run* (a
|
|
17
17
|
`script`, §1.1–§1.4); on a **device**, a **transporter** and a **replenisher** it says
|
|
18
18
|
*how to reach the machine* (a `connection`, §1.5), which is what lets a script be the
|
|
19
|
-
commands alone (§1.7). The division is the same each time: the machine has an address,
|
|
19
|
+
commands alone (§1.7 for SiLA2, §1.10 for LADS OPC UA). The division is the same each time: the machine has an address,
|
|
20
20
|
and the thing it does to something else has a procedure. Nowhere else — see §1.8.
|
|
21
21
|
|
|
22
22
|
An environment process mode (§5) may carry an `x-labcode` mapping holding a `script`: the
|
|
@@ -46,17 +46,18 @@ still validates and schedules as plain v0. Only labcode interprets it.
|
|
|
46
46
|
- `x-labcode.script`, if present, MUST be a mapping with:
|
|
47
47
|
- `language`: MUST be `python`.
|
|
48
48
|
- `code`: MUST be a string (an implementation-provided Python function body).
|
|
49
|
-
- `flavor` (optional, default `raw`): MUST be `raw` or `
|
|
50
|
-
be run (§1.7)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`sila2`
|
|
49
|
+
- `flavor` (optional, default `raw`): MUST be `raw`, `sila2` or `lads` — how `code` is
|
|
50
|
+
meant to be run. `sila2` (§1.7) is the **recommended** way to drive a SiLA2 lab, and
|
|
51
|
+
`lads` (§1.10) a LADS OPC UA one: the code is the commands alone and labcode supplies
|
|
52
|
+
the clients. `raw` is the whole function body, written by its author — the general
|
|
53
|
+
escape hatch, and what a script that connects for itself (or speaks some other
|
|
54
|
+
protocol) uses. On a **replenishment route** `sila2` and `lads` are errors in this
|
|
55
|
+
version (§1.4).
|
|
55
56
|
- `endpoints` (**transport routes only**, optional, default `false`): MUST be a boolean —
|
|
56
57
|
whether this move is also given clients for the devices at either **end** of its route,
|
|
57
58
|
not only its `transporter` (§1.7). A process mode may not declare it: a mode's machines
|
|
58
|
-
are the ones it lists. A `sila2` script on a route with **no transporter**
|
|
59
|
-
declare it `true`: the ends are then the only machines there are, and a script is never
|
|
59
|
+
are the ones it lists. A `sila2` or `lads` script on a route with **no transporter**
|
|
60
|
+
(§1.3) MUST declare it `true`: the ends are then the only machines there are, and a script is never
|
|
60
61
|
given a machine it did not ask for.
|
|
61
62
|
|
|
62
63
|
**Unknown keys are an error** — in `x-labcode` at every position, and in the mappings it
|
|
@@ -134,7 +135,8 @@ by `(transporter, from, to)`, so a null one matches a null one, and the script's
|
|
|
134
135
|
`transporter` local is `None`.
|
|
135
136
|
|
|
136
137
|
Such a move is performed by the **devices at either end of the route** — for a move within
|
|
137
|
-
one device, that device. So a `flavor: sila2` script on such a route must
|
|
138
|
+
one device, that device. So a `flavor: sila2` (or `lads`) script on such a route must
|
|
139
|
+
declare
|
|
138
140
|
**`endpoints: true`** (§1.6), and at least one of those devices must declare a
|
|
139
141
|
`connection`; either missing is a front-door error. The `endpoints` request is *required*
|
|
140
142
|
rather than inferred: it is the author's statement of which machines the script drives, and
|
|
@@ -178,8 +180,8 @@ A replenishment script is **side-effect only**, as a transport's is: its return
|
|
|
178
180
|
ignored and no output is verified. An exception is a graceful failure — the refill ends
|
|
179
181
|
`failed` and the run stops, like any activity failure.
|
|
180
182
|
|
|
181
|
-
`flavor: sila2` is **an error** on a replenishment route in this version
|
|
182
|
-
is handed clients (§1.7), and which machine's clients a refill should receive — the
|
|
183
|
+
`flavor: sila2` is **an error** on a replenishment route in this version, and so is
|
|
184
|
+
`flavor: lads`. Such a script is handed clients (§1.7, §1.10), and which machine's clients a refill should receive — the
|
|
183
185
|
replenisher's, or both ends' as a transport route may ask for — is not settled. Refusing
|
|
184
186
|
says so; running the script without the clients it asked for would not. Use `raw` (the
|
|
185
187
|
default), which may of course connect for itself.
|
|
@@ -204,6 +206,17 @@ devices:
|
|
|
204
206
|
x-labcode:
|
|
205
207
|
connection: { kind: sila2, host: 127.0.0.1, port: 50053, insecure: true }
|
|
206
208
|
|
|
209
|
+
- id: thermal_cycler
|
|
210
|
+
spots: [block]
|
|
211
|
+
x-labcode:
|
|
212
|
+
connection:
|
|
213
|
+
kind: lads
|
|
214
|
+
host: 127.0.0.1
|
|
215
|
+
port: 4844
|
|
216
|
+
insecure: true
|
|
217
|
+
device: ThermalCycler # optional: which LADS device on that server...
|
|
218
|
+
unit: Cycler # ...and which of its functional units (§1.10)
|
|
219
|
+
|
|
207
220
|
transporters:
|
|
208
221
|
- id: arm
|
|
209
222
|
x-labcode:
|
|
@@ -212,13 +225,22 @@ transporters:
|
|
|
212
225
|
|
|
213
226
|
| field | required | default | meaning |
|
|
214
227
|
|---|---|---|---|
|
|
215
|
-
| `kind` | no | `sila2` | the protocol
|
|
228
|
+
| `kind` | no | `sila2` | the protocol: `sila2` or `lads` (LADS OPC UA) |
|
|
216
229
|
| `host` | **yes** | — | a non-empty string |
|
|
217
230
|
| `port` | **yes** | — | an integer in 1..65535 |
|
|
218
|
-
| `insecure` | no | `false` | connect without TLS |
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
231
|
+
| `insecure` | no | `false` | connect without TLS (for `lads`: with OPC UA security mode None) |
|
|
232
|
+
| `device` | no | the only one | `lads` only: the browse name of the LADS device on the server |
|
|
233
|
+
| `unit` | no | the only one | `lads` only: the browse name of that device's functional unit |
|
|
234
|
+
|
|
235
|
+
`device` and `unit` are a **LADS** address and nothing else: on a `sila2` connection either
|
|
236
|
+
one is an error, and each MUST be a non-empty string. Omitted, they mean *the only one there
|
|
237
|
+
is* — a server with one device of one unit, which is the common case, needs neither — and
|
|
238
|
+
the omission is resolved when the script connects, not at the front door, since only the
|
|
239
|
+
server knows what it holds (§1.10).
|
|
240
|
+
|
|
241
|
+
**TLS is not supported in this version** — for `lads`, no OPC UA security mode other than
|
|
242
|
+
None. There is nowhere in the schema to put the credentials either needs (a root
|
|
243
|
+
certificate, a client certificate), so a `connection` whose effective
|
|
222
244
|
`insecure` is false is rejected at the front door — including one that simply omits the
|
|
223
245
|
key and takes the default — and refused again if a script reaches the connect helper
|
|
224
246
|
directly. Every connection must therefore say `insecure: true` today.
|
|
@@ -226,12 +248,13 @@ The default stays `false` so that supporting TLS later is a pure addition: new f
|
|
|
226
248
|
and the error goes away. The check applies to every declared `connection`, whether or not
|
|
227
249
|
a script uses it.
|
|
228
250
|
|
|
229
|
-
**A `sila2` script needs somewhere to connect**
|
|
251
|
+
**A `sila2` or `lads` script needs somewhere to connect** — a `connection` of its own
|
|
252
|
+
`kind` (checked at the front door):
|
|
230
253
|
|
|
231
254
|
- a mode script with `flavor: sila2` requires **at least one** of that mode's `devices[]`
|
|
232
|
-
to declare a `connection
|
|
233
|
-
- a transport script
|
|
234
|
-
|
|
255
|
+
to declare a `sila2` `connection`, and one with `flavor: lads` a `lads` one;
|
|
256
|
+
- a transport script requires that route's `transporter` to declare one of the script's
|
|
257
|
+
kind — or, on a route with **no transporter** (§1.3), requires `endpoints: true` and at
|
|
235
258
|
least one of the devices at its ends to declare one, those being the machines that
|
|
236
259
|
perform such a move.
|
|
237
260
|
|
|
@@ -240,7 +263,8 @@ either **end** of its route (§1.7), but those are *not* required to declare a `
|
|
|
240
263
|
a route through a plain holding location is ordinary, and the end without an address is
|
|
241
264
|
simply not connected to (a **warning** when *neither* end has one, since then the request
|
|
242
265
|
does nothing — an **error** on a route with no transporter, which has nothing else to
|
|
243
|
-
drive).
|
|
266
|
+
drive). An end whose `connection` is of the **other** kind is not connected to either, and
|
|
267
|
+
is warned about: a script speaks one protocol (§1.10). The transporter is the one that must be reachable, because it is the machine that
|
|
244
268
|
does the moving — and the one `sila2_client` names; where there is none, that is the source
|
|
245
269
|
device, for the same reason. Asking a `raw` script for endpoint
|
|
246
270
|
clients is an **error**: a raw script is handed no clients at all, so the request cannot be
|
|
@@ -420,7 +444,7 @@ x-labcode:
|
|
|
420
444
|
more: waiting for an observable command to finish (the standard `sila2` polling pattern)
|
|
421
445
|
belongs in the code, as it does in a `raw` script.
|
|
422
446
|
|
|
423
|
-
#### 1.
|
|
447
|
+
#### 1.7.1 `labcode.sila2_commands` — the polling loop, written once
|
|
424
448
|
|
|
425
449
|
Waiting for an observable command is the same loop in every script that issues one, so
|
|
426
450
|
labcode ships it. It is an **ordinary module**, reached by an ordinary import — nothing is
|
|
@@ -513,6 +537,108 @@ x-labcode:
|
|
|
513
537
|
- `lc run` overrides it for one run: `--op-timeout SECONDS`, or `--no-op-timeout` for no
|
|
514
538
|
limit at all. The order is flag, then document, then default.
|
|
515
539
|
|
|
540
|
+
### 1.10 Calling convention (`flavor: lads`)
|
|
541
|
+
|
|
542
|
+
A `lads` script is the LADS OPC UA counterpart of a `sila2` one (§1.7), and everything §1.7
|
|
543
|
+
says of the flavor holds for it with the names changed: which machines an operation is
|
|
544
|
+
handed and in what order, the `endpoints` request on a transport, connections lasting one
|
|
545
|
+
operation and closed on any exit, a machine held without a client explaining itself when
|
|
546
|
+
indexed (`lads_not_connected`, `lads_endpoints_not_requested`), and the rest of the work
|
|
547
|
+
being the script's own. The two names it sees are:
|
|
548
|
+
|
|
549
|
+
| name | meaning |
|
|
550
|
+
|---|---|
|
|
551
|
+
| `lads_clients` | a `LadsUnit` for each machine, by **machine id**, in the order of §1.7 |
|
|
552
|
+
| `lads_client` | the first of them — for a transport its `transporter`, or the source device on a route with none |
|
|
553
|
+
|
|
554
|
+
Both are reserved, as `sila2_clients` / `sila2_client` are, and only in a `lads` script.
|
|
555
|
+
|
|
556
|
+
```yaml
|
|
557
|
+
x-labcode:
|
|
558
|
+
script:
|
|
559
|
+
language: python
|
|
560
|
+
flavor: lads
|
|
561
|
+
code: |
|
|
562
|
+
# `lads_client` is the sealer's functional unit, already connected.
|
|
563
|
+
lads_client.write_target("SealingTemperature", 170)
|
|
564
|
+
lads_client.run_program("StartCycle") # returns once the run has ended
|
|
565
|
+
return {"cycle_count": int(lads_client.read("CycleCount"))} # a vendor variable
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
- **What a script is handed is one functional unit, not a raw client.** A SiLA2 client is
|
|
569
|
+
typed by its server's features, so a `sila2` script reads well with nothing in between; an
|
|
570
|
+
OPC UA client is not, and a script driving LADS through it directly would be mostly
|
|
571
|
+
namespaces, NodeIds and Variants. A `LadsUnit` is a thin, synchronous view of the unit
|
|
572
|
+
named by the connection's `device` / `unit` (§1.5), built only on what the LADS companion
|
|
573
|
+
specification (OPC 30500, LADS 1.0.0) defines:
|
|
574
|
+
|
|
575
|
+
| call | LADS |
|
|
576
|
+
|---|---|
|
|
577
|
+
| `run_program(template_id, **properties)` | `StartProgram`, then wait for the run's result in `ProgramManager/ResultSet`; returns its properties (§1.10.1) |
|
|
578
|
+
| `start_program(template_id, **properties)` | `StartProgram` alone; returns the run id |
|
|
579
|
+
| `results()` | the result nodes in `ProgramManager/ResultSet` |
|
|
580
|
+
| `stop()`, `abort()`, `clear()` | the unit state machine's methods, each waiting until the unit has settled; returns that state |
|
|
581
|
+
| `reset()` | `abort()` then `clear()`, failing unless the unit ends `Stopped` — the counterpart of a SiLA2 `Reset` |
|
|
582
|
+
| `state(owner="FunctionalUnitState")` | a state machine's `CurrentState` (`Stopped`, `Running`, …) |
|
|
583
|
+
| `cover(name, action)` | a cover function's `Open` / `Close`, waiting until it has settled, and failing unless it ends `Opened` / `Closed` |
|
|
584
|
+
| `write_target(function, value)` | a control function's `TargetValue` |
|
|
585
|
+
| `read(path)`, `write(path, value)`, `child(path)` | any node below the unit, by browse names joined with `/`, in whichever namespace they live |
|
|
586
|
+
| `call(owner, method, *variants)` | any LADS method of the object at `owner` |
|
|
587
|
+
| `raw` | the asyncua client itself, for anything none of this covers |
|
|
588
|
+
|
|
589
|
+
A method call or write the server refuses fails the operation (`lads_call_failed`). Where
|
|
590
|
+
the server adds a `LastError` variable to the unit — not part of LADS; the reference lab's
|
|
591
|
+
servers do — the failure quotes it, since a StatusCode says *that* the server refused and
|
|
592
|
+
not *why*.
|
|
593
|
+
- **Which unit is resolved when connecting.** labcode looks for LADS devices under the
|
|
594
|
+
server's DI `DeviceSet` and for functional units in the chosen device's
|
|
595
|
+
`FunctionalUnitSet`, recognising each by its type (a subtype counts). A `device` or `unit`
|
|
596
|
+
that is named but absent fails the operation (`lads_target_not_found`), as does an omitted
|
|
597
|
+
one where the server has several (`lads_ambiguous_target`, naming them) and a server that
|
|
598
|
+
is not a LADS server at all (`lads_not_a_lads_server`). The front door cannot check any of
|
|
599
|
+
this: it needs the server.
|
|
600
|
+
- **One protocol per script.** An operation may hold machines of both kinds — a LADS sealer
|
|
601
|
+
loaded by a SiLA2 arm — but a script is handed clients of its own flavor only. A held
|
|
602
|
+
machine whose `connection` is of the other kind is treated as one held without a client,
|
|
603
|
+
and indexing it says so (`lads_other_protocol`, or `sila2_other_protocol` the other way
|
|
604
|
+
round). A mode none of whose devices is of the script's kind is an error at the front door
|
|
605
|
+
(§1.5); an operation that must command both kinds is written `raw`.
|
|
606
|
+
- **The client library is the `lads` extra** (`asyncua`), needed by the interpreter that
|
|
607
|
+
runs the scripts. labcode imports it only when a `lads` script connects, so it runs
|
|
608
|
+
without it otherwise; a `lads` script without it fails with `lads_unavailable`.
|
|
609
|
+
- **Probing is unchanged** (§1.6): it opens a TCP connection, whatever the `kind`.
|
|
610
|
+
- **A run's record** (`lc run --trace`) holds a `lads` operation's span, but nothing inside
|
|
611
|
+
it yet: the connection and the commands are traced for SiLA2 only (§5).
|
|
612
|
+
|
|
613
|
+
#### 1.10.1 `labcode.lads_commands` — the waits, written once
|
|
614
|
+
|
|
615
|
+
A LADS method returns once the server has **begun** carrying it out, and the effect follows:
|
|
616
|
+
`StartProgram` returns a run id while the program runs, `Stop` leaves the unit `Stopping`, a
|
|
617
|
+
cover passes through `Opening`. So the waits are the script's, as `settle` is in §1.7.1, and
|
|
618
|
+
labcode ships them in an ordinary module. A script normally reaches them through its unit
|
|
619
|
+
(`lads_client.run_program(...)`, `lads_client.stop()`), which calls them; importing them
|
|
620
|
+
reserves nothing, as with `settle`.
|
|
621
|
+
|
|
622
|
+
- `run_program(unit, template_id, *, timeout=3600.0, poll=1.0, **properties)` starts the
|
|
623
|
+
program and waits for the result whose `DeviceProgramRunId` (or, failing that, browse
|
|
624
|
+
name) is the run id. The **result** is the end of a run, not the unit's state returning to
|
|
625
|
+
`Stopped`: a server records it once the run is over and its effects are visible. The
|
|
626
|
+
properties are passed as LADS' `KeyValueType[]`, stringified.
|
|
627
|
+
- **A run that did not complete fails the operation** (`lads_program_failed`). LADS does not
|
|
628
|
+
say how a result reports failure, so labcode reads an `Outcome` property where the server
|
|
629
|
+
records one (anything but `Completed` is a failure) and otherwise the unit's state (a unit
|
|
630
|
+
left `Aborted` failed).
|
|
631
|
+
- `wait_for_state(unit, *, owner="FunctionalUnitState", label, timeout=600.0, poll=0.2)`
|
|
632
|
+
waits until a state machine has left every transient state (`Stopping`, `Aborting`,
|
|
633
|
+
`Clearing`, `Starting`, `Opening`, `Closing`) and returns the state it settled in, or fails
|
|
634
|
+
with `lads_transition_timeout`.
|
|
635
|
+
- **A timeout is not a cancel**, as in §1.7.1 — though here it could be: LADS has `Abort`.
|
|
636
|
+
A wait that times out (`lads_program_timeout`) still fails only the operation and leaves the
|
|
637
|
+
instrument as it is, so the same mistake leaves the lab in the same state whichever
|
|
638
|
+
protocol the instrument speaks. A script that wants the abort calls `abort()` itself.
|
|
639
|
+
- The timeouts are real seconds, the inner limit under `op_timeout` (§1.9), and unrelated to
|
|
640
|
+
a mode's `duration`, exactly as `settle`'s are.
|
|
641
|
+
|
|
516
642
|
## 2. Code source resolution and exclusivity
|
|
517
643
|
|
|
518
644
|
For a dispatched `(process, mode)`, labcode resolves the code to run in this order:
|
|
@@ -646,13 +772,20 @@ ids per physical Object swaps in `RealUuid4Generator` (via
|
|
|
646
772
|
|
|
647
773
|
## 5. Not yet in this version (roadmap)
|
|
648
774
|
|
|
649
|
-
- **`flavor: sila2` on a replenishment route** — refused today (§1.4). What has
|
|
650
|
-
settled first is which machine's clients a refill script receives: the
|
|
651
|
-
alone, or both ends' as a transport route may ask for with `endpoints`.
|
|
652
|
-
refill that must speak SiLA2 uses a `raw` script and connects for
|
|
775
|
+
- **`flavor: sila2` or `lads` on a replenishment route** — refused today (§1.4). What has
|
|
776
|
+
to be settled first is which machine's clients a refill script receives: the
|
|
777
|
+
replenisher's alone, or both ends' as a transport route may ask for with `endpoints`.
|
|
778
|
+
Until then a refill that must speak SiLA2 or LADS uses a `raw` script and connects for
|
|
779
|
+
itself.
|
|
653
780
|
- **A deeper probe** — asking a machine something (a SiLA2 property read) rather than only
|
|
654
781
|
opening a connection to it, so "answering" can be checked and not just "listening"
|
|
655
782
|
(§1.6). It would be an opt-in depth, since it costs a real exchange per check.
|
|
656
783
|
- **Probing in parallel** — checking machines concurrently, so a lab with many unreachable
|
|
657
784
|
machines does not pay for them one timeout at a time (§1.6).
|
|
658
|
-
- **TLS** — the fields a secure connection needs, lifting the restriction in §1.5
|
|
785
|
+
- **TLS** — the fields a secure connection needs, lifting the restriction in §1.5; for
|
|
786
|
+
LADS, an OPC UA security policy and the certificates it takes.
|
|
787
|
+
- **Tracing a `lads` operation** — spans for the connection and each method call inside an
|
|
788
|
+
operation's span, as a `sila2` one has (§1.10).
|
|
789
|
+
- **Aborting on a timeout, as an opt-in** — LADS can cancel a run where SiLA2 cannot, so a
|
|
790
|
+
`lads` wait could abort what it gave up on. It is off (§1.10.1) until it can be asked for
|
|
791
|
+
per call, since the state an abort leaves is not always better than the one a run leaves.
|