labcode 0.8.0__tar.gz → 0.10.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.
Files changed (114) hide show
  1. {labcode-0.8.0 → labcode-0.10.0}/.github/workflows/ci.yml +15 -0
  2. labcode-0.10.0/CHANGELOG.md +107 -0
  3. {labcode-0.8.0 → labcode-0.10.0}/PKG-INFO +65 -20
  4. {labcode-0.8.0 → labcode-0.10.0}/README.md +55 -16
  5. {labcode-0.8.0 → labcode-0.10.0/docs}/SPECIFICATIONS.md +201 -56
  6. {labcode-0.8.0 → labcode-0.10.0}/examples/README.md +65 -15
  7. labcode-0.10.0/examples/lads_plate_cycle.env.yaml +288 -0
  8. labcode-0.10.0/examples/lads_seal.env.yaml +100 -0
  9. {labcode-0.8.0 → labcode-0.10.0}/examples/preflight_sila2_env.py +24 -9
  10. {labcode-0.8.0 → labcode-0.10.0}/examples/render_sila2_plate_cycle.py +3 -2
  11. labcode-0.10.0/examples/run_all_lads_examples.py +93 -0
  12. {labcode-0.8.0 → labcode-0.10.0}/examples/run_all_sila2_examples.py +1 -1
  13. {labcode-0.8.0 → labcode-0.10.0}/examples/run_sila2_plate_cycle.py +9 -5
  14. {labcode-0.8.0 → labcode-0.10.0}/examples/run_sila2_plate_cycle_no_atc.py +5 -5
  15. {labcode-0.8.0 → labcode-0.10.0}/examples/run_sila2_seal.py +4 -3
  16. {labcode-0.8.0 → labcode-0.10.0}/examples/sila2_plate_cycle.boundary.yaml +1 -1
  17. {labcode-0.8.0 → labcode-0.10.0}/examples/sila2_plate_cycle.wrapped.env.yaml +7 -7
  18. {labcode-0.8.0 → labcode-0.10.0}/examples/sila2_plate_cycle_no_atc.boundary.yaml +1 -1
  19. {labcode-0.8.0 → labcode-0.10.0}/examples/sila2_plate_cycle_no_atc.wrapped.env.yaml +7 -7
  20. {labcode-0.8.0 → labcode-0.10.0}/examples/sila2_seal.boundary.yaml +1 -1
  21. {labcode-0.8.0 → labcode-0.10.0}/examples/sila2_seal.env.yaml +2 -2
  22. {labcode-0.8.0 → labcode-0.10.0}/examples/sila2_seal.wrapped.env.yaml +4 -4
  23. {labcode-0.8.0 → labcode-0.10.0}/labcode/_child.py +8 -9
  24. {labcode-0.8.0 → labcode-0.10.0}/labcode/backend.py +114 -54
  25. {labcode-0.8.0 → labcode-0.10.0}/labcode/cli.py +16 -1
  26. labcode-0.10.0/labcode/connections.py +257 -0
  27. {labcode-0.8.0 → labcode-0.10.0}/labcode/dialect.py +98 -40
  28. {labcode-0.8.0 → labcode-0.10.0}/labcode/extension.py +44 -7
  29. labcode-0.10.0/labcode/lads.py +181 -0
  30. labcode-0.10.0/labcode/lads_commands.py +167 -0
  31. labcode-0.10.0/labcode/lads_unit.py +331 -0
  32. {labcode-0.8.0 → labcode-0.10.0}/labcode/objectid.py +122 -33
  33. {labcode-0.8.0 → labcode-0.10.0}/labcode/probe.py +4 -4
  34. {labcode-0.8.0 → labcode-0.10.0}/labcode/run_cli.py +5 -0
  35. {labcode-0.8.0 → labcode-0.10.0}/labcode/runner.py +20 -3
  36. labcode-0.10.0/labcode/sila2.py +154 -0
  37. {labcode-0.8.0 → labcode-0.10.0}/labcode/sila2_commands.py +1 -1
  38. {labcode-0.8.0 → labcode-0.10.0}/labcode.egg-info/PKG-INFO +65 -20
  39. {labcode-0.8.0 → labcode-0.10.0}/labcode.egg-info/SOURCES.txt +13 -1
  40. {labcode-0.8.0 → labcode-0.10.0}/labcode.egg-info/requires.txt +11 -3
  41. {labcode-0.8.0 → labcode-0.10.0}/labcode.egg-info/scm_file_list.json +13 -1
  42. labcode-0.10.0/labcode.egg-info/scm_version.json +8 -0
  43. {labcode-0.8.0 → labcode-0.10.0}/pyproject.toml +34 -10
  44. labcode-0.10.0/tests/fixtures/dispense_read.workflow.yaml +81 -0
  45. {labcode-0.8.0 → labcode-0.10.0}/tests/test_backend.py +162 -5
  46. {labcode-0.8.0 → labcode-0.10.0}/tests/test_cli.py +16 -1
  47. {labcode-0.8.0 → labcode-0.10.0}/tests/test_dialect.py +135 -6
  48. labcode-0.10.0/tests/test_lads.py +184 -0
  49. labcode-0.10.0/tests/test_lads_unit.py +396 -0
  50. {labcode-0.8.0 → labcode-0.10.0}/tests/test_objectid.py +109 -0
  51. {labcode-0.8.0 → labcode-0.10.0}/tests/test_run_cli.py +32 -18
  52. {labcode-0.8.0 → labcode-0.10.0}/tests/test_sila2.py +2 -2
  53. labcode-0.10.0/tests/test_structured_labcode.py +135 -0
  54. labcode-0.8.0/labcode/sila2.py +0 -280
  55. labcode-0.8.0/labcode.egg-info/scm_version.json +0 -8
  56. {labcode-0.8.0 → labcode-0.10.0}/.github/workflows/publish.yml +0 -0
  57. {labcode-0.8.0 → labcode-0.10.0}/.gitignore +0 -0
  58. {labcode-0.8.0 → labcode-0.10.0}/LICENSE +0 -0
  59. {labcode-0.8.0 → labcode-0.10.0}/MANIFEST.in +0 -0
  60. {labcode-0.8.0 → labcode-0.10.0}/examples/outputs/plate_line.boundary.yaml +0 -0
  61. {labcode-0.8.0 → labcode-0.10.0}/examples/outputs/plate_line.observation.yaml +0 -0
  62. {labcode-0.8.0 → labcode-0.10.0}/examples/outputs/plate_line.plan.yaml +0 -0
  63. {labcode-0.8.0 → labcode-0.10.0}/examples/outputs/plate_line.svg +0 -0
  64. {labcode-0.8.0 → labcode-0.10.0}/examples/outputs/shared_bench.boundary.yaml +0 -0
  65. {labcode-0.8.0 → labcode-0.10.0}/examples/outputs/shared_bench.observation.yaml +0 -0
  66. {labcode-0.8.0 → labcode-0.10.0}/examples/outputs/shared_bench.plan.yaml +0 -0
  67. {labcode-0.8.0 → labcode-0.10.0}/examples/outputs/shared_bench.svg +0 -0
  68. {labcode-0.8.0 → labcode-0.10.0}/examples/outputs/sila2_plate_cycle.boundary.yaml +0 -0
  69. {labcode-0.8.0 → labcode-0.10.0}/examples/outputs/sila2_plate_cycle.observation.yaml +0 -0
  70. {labcode-0.8.0 → labcode-0.10.0}/examples/outputs/sila2_plate_cycle.plan.yaml +0 -0
  71. {labcode-0.8.0 → labcode-0.10.0}/examples/outputs/sila2_plate_cycle.svg +0 -0
  72. {labcode-0.8.0 → labcode-0.10.0}/examples/plate_line.boundary.yaml +0 -0
  73. {labcode-0.8.0 → labcode-0.10.0}/examples/plate_line.env.yaml +0 -0
  74. {labcode-0.8.0 → labcode-0.10.0}/examples/plate_line.workflow.yaml +0 -0
  75. {labcode-0.8.0 → labcode-0.10.0}/examples/render_plate_line.py +0 -0
  76. {labcode-0.8.0 → labcode-0.10.0}/examples/render_shared_bench.py +0 -0
  77. {labcode-0.8.0 → labcode-0.10.0}/examples/shared_bench.afternoon.boundary.yaml +0 -0
  78. {labcode-0.8.0 → labcode-0.10.0}/examples/shared_bench.env.yaml +0 -0
  79. {labcode-0.8.0 → labcode-0.10.0}/examples/shared_bench.morning.boundary.yaml +0 -0
  80. {labcode-0.8.0 → labcode-0.10.0}/examples/shared_bench.run.yaml +0 -0
  81. {labcode-0.8.0 → labcode-0.10.0}/examples/shared_bench.workflow.yaml +0 -0
  82. {labcode-0.8.0 → labcode-0.10.0}/examples/sila2_plate_cycle.workflow.yaml +0 -0
  83. {labcode-0.8.0 → labcode-0.10.0}/examples/sila2_plate_cycle_no_atc.workflow.yaml +0 -0
  84. {labcode-0.8.0 → labcode-0.10.0}/examples/sila2_seal.workflow.yaml +0 -0
  85. {labcode-0.8.0 → labcode-0.10.0}/labcode/__init__.py +0 -0
  86. {labcode-0.8.0 → labcode-0.10.0}/labcode/__main__.py +0 -0
  87. {labcode-0.8.0 → labcode-0.10.0}/labcode/idgen.py +0 -0
  88. {labcode-0.8.0 → labcode-0.10.0}/labcode/otel.py +0 -0
  89. {labcode-0.8.0 → labcode-0.10.0}/labcode/otel_sila2.py +0 -0
  90. {labcode-0.8.0 → labcode-0.10.0}/labcode/py.typed +0 -0
  91. {labcode-0.8.0 → labcode-0.10.0}/labcode/record.py +0 -0
  92. {labcode-0.8.0 → labcode-0.10.0}/labcode/sila2_instrument.py +0 -0
  93. {labcode-0.8.0 → labcode-0.10.0}/labcode.egg-info/dependency_links.txt +0 -0
  94. {labcode-0.8.0 → labcode-0.10.0}/labcode.egg-info/entry_points.txt +0 -0
  95. {labcode-0.8.0 → labcode-0.10.0}/labcode.egg-info/top_level.txt +0 -0
  96. {labcode-0.8.0 → labcode-0.10.0}/setup.cfg +0 -0
  97. {labcode-0.8.0 → labcode-0.10.0}/tests/fixtures/device_script.env.yaml +0 -0
  98. {labcode-0.8.0 → labcode-0.10.0}/tests/fixtures/device_script.workflow.yaml +0 -0
  99. {labcode-0.8.0 → labcode-0.10.0}/tests/fixtures/joint.run.yaml +0 -0
  100. {labcode-0.8.0 → labcode-0.10.0}/tests/fixtures/replenishment.env.yaml +0 -0
  101. {labcode-0.8.0 → labcode-0.10.0}/tests/fixtures/reroute_device.env.yaml +0 -0
  102. {labcode-0.8.0 → labcode-0.10.0}/tests/fixtures/reroute_transporter.env.yaml +0 -0
  103. {labcode-0.8.0 → labcode-0.10.0}/tests/fixtures/transport.env.yaml +0 -0
  104. {labcode-0.8.0 → labcode-0.10.0}/tests/fixtures/transport.workflow.yaml +0 -0
  105. {labcode-0.8.0 → labcode-0.10.0}/tests/test_joint.py +0 -0
  106. {labcode-0.8.0 → labcode-0.10.0}/tests/test_otel.py +0 -0
  107. {labcode-0.8.0 → labcode-0.10.0}/tests/test_otel_child.py +0 -0
  108. {labcode-0.8.0 → labcode-0.10.0}/tests/test_otel_grpc.py +0 -0
  109. {labcode-0.8.0 → labcode-0.10.0}/tests/test_otel_sila2.py +0 -0
  110. {labcode-0.8.0 → labcode-0.10.0}/tests/test_probe.py +0 -0
  111. {labcode-0.8.0 → labcode-0.10.0}/tests/test_record.py +0 -0
  112. {labcode-0.8.0 → labcode-0.10.0}/tests/test_recording.py +0 -0
  113. {labcode-0.8.0 → labcode-0.10.0}/tests/test_sila2_commands.py +0 -0
  114. {labcode-0.8.0 → labcode-0.10.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,107 @@
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.10.0 — 2026-10-05
10
+
11
+ labcode runs a protocol repeated over every plate (`map` / `fold`), and stops making up
12
+ a value a script did not compute.
13
+
14
+ ### Added
15
+
16
+ - **`map` and `fold`** run, as ofplang-schedule 0.13 expands them and ofplang-run 0.13
17
+ executes the expansion. An **Array of Objects** at the run boundary is one Object per
18
+ element: its `view` is a list of views, one per spot, and each element gets its own
19
+ `_id`, keyed by the element (`plates[1]`, §4.3). An invocation's node path carries its
20
+ iteration index, and so do the keys of what it creates.
21
+ - **What a run made up, it says** (`lc run` prints each as `lc run: warning: ...`):
22
+ - `entry_input_defaulted` — a boundary view written in part, or not at all: the
23
+ declared fields it left out run on their type's default (§4.2). Once per Object.
24
+ - `output_view_defaulted` — a script that did not return an Object it created: the
25
+ view runs on its type's default (§1.2). Once per process and port.
26
+ - `_id` is never reported; minting it is labcode's identity, not a default.
27
+
28
+ ### Changed — may need your attention
29
+
30
+ - **A script must return every Pure Data output it declares** (§1.2). Until now one left
31
+ out took its type's default — `return {}` from a `read` gave `od: 0.0`, a reading
32
+ nothing took. It now fails the operation (`script_output_names`), as v0 §22.2 does. An
33
+ Object output may still be left out: a mapped one is carried, a created one gets its
34
+ default view (reported).
35
+ - **A process with no script anywhere that declares a Pure Data output is refused** by
36
+ `lc run` before anything runs (§2): a no-op can compute nothing, so every run of it
37
+ would fail. One with only Object outputs still runs as a no-op, with the warning it
38
+ always had. A process with a script on some modes only fails if a mode without one is
39
+ chosen.
40
+ - **A boundary view that is not a mapping** (or, for an Array, not a list of them) is
41
+ refused. It used to be replaced by an empty view without a word.
42
+ - **An Object mapped from an input with no `_id`** fails the operation
43
+ (`missing_object_id`). No run can reach this; it is there to catch a broken invariant
44
+ early.
45
+ - A v0 §22 workflow script in a language other than Python fails the operation
46
+ (`script_language`) instead of running as a no-op.
47
+ - Requires ofplang-validate 0.3, ofplang-schedule 0.13 and ofplang-run 0.13
48
+ (specification revision 0.4).
49
+
50
+ `lc export` is unchanged; drawing a run that has `map` / `fold` invocations in it is not
51
+ yet supported there.
52
+
53
+ ## 0.9.0 — 2026-09-28
54
+
55
+ labcode can now drive a lab over **LADS OPC UA** as well as SiLA2, and gains `lc export`.
56
+ Both are optional extras; nothing changes for an environment that uses neither.
57
+
58
+ ### Added
59
+
60
+ - **`flavor: lads`** — a script that is the commands alone, handed its LADS OPC UA clients
61
+ as a `sila2` script is handed SiLA2 ones (§1.10).
62
+ - A connection may say `kind: lads`, and may name the `device` and functional `unit` on the
63
+ server; left out, each is the only one there is (§1.5).
64
+ - The script sees `lads_clients` / `lads_client`: one functional unit per machine, with the
65
+ program runs, state-machine methods, cover movements and set-points LADS defines, each
66
+ waiting for what it starts, and `.raw` for the asyncua client itself.
67
+ - `labcode.lads_commands` holds the waits: a program run to its result, a state machine to
68
+ settle (§1.10.1). A timeout fails the operation and aborts nothing, as SiLA2's does.
69
+ - A script speaks one protocol. An operation may hold machines of both kinds, but a script
70
+ commands only its own flavor's; reaching for the other kind says why
71
+ (`lads_other_protocol` / `sila2_other_protocol`).
72
+ - The client library is the `lads` extra (`asyncua`). labcode imports it only when a `lads`
73
+ script connects.
74
+ - **`lc export`** — forwards to [ofplang-export](https://github.com/ofplang/export)'s CLI
75
+ unchanged, as `lc validate` and `lc schedule` do. `lc export view plan.yaml -o plan.html`
76
+ writes the workflow and its plan as one self-contained HTML file. It is the `export` extra;
77
+ without it `lc export` names the extra and exits 2.
78
+ - **LADS examples** — `lads_seal.env.yaml` and `lads_plate_cycle.env.yaml` run the
79
+ `sila2_seal` and `sila2_plate_cycle` workflows unchanged, with every machine reached over
80
+ LADS. `run_all_lads_examples.py` runs both against the reference lab,
81
+ [ofplang/mocklab](https://github.com/ofplang/mocklab).
82
+
83
+ ### Changed
84
+
85
+ - `SPECIFICATIONS.md` moved to `docs/SPECIFICATIONS.md`.
86
+ - The `ofplang-schedule` cap is `<0.13`, admitting schedule 0.12. The floor is unchanged.
87
+ - The front door checks a connecting script's connection **per kind**. A `sila2` or `lads`
88
+ script needs a machine of its own kind. An endpoint of the other kind draws a warning, as
89
+ an endpoint with no connection does. Messages that named only `sila2` now name the flavor.
90
+ - What a connecting flavor needs (planning the clients, holding them for one operation,
91
+ generating the wrapper) moved from `labcode.sila2` into `labcode.connections`. The `sila2`
92
+ flavor's behaviour, messages and reason codes are unchanged.
93
+
94
+ ### Fixed
95
+
96
+ - `examples/preflight_sila2_env.py` treated every declared connection as SiLA2. An
97
+ environment with a LADS machine would have reported it as a failing SiLA2 server. It now
98
+ checks SiLA2 machines only and lists the others as not checked.
99
+
100
+ ### Documentation
101
+
102
+ - The reference lab is named by its current name, ofplang/mocklab (formerly
103
+ ofplang-sila2-backend), and the examples record the commit they were last verified against.
104
+ - Section references written before probing took §1.6 pointed at the wrong sections and are
105
+ corrected. The `labcode.sila2_commands` section is numbered §1.7.1.
106
+ - The README lists the optional extras, lists every example, and links by absolute URL, so the
107
+ links also work on PyPI.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labcode
3
- Version: 0.8.0
3
+ Version: 0.10.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
@@ -20,21 +20,27 @@ Classifier: Typing :: Typed
20
20
  Requires-Python: >=3.10
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: ofplang-validate<0.3,>=0.2
24
- Requires-Dist: ofplang-schedule<0.12,>=0.9
25
- Requires-Dist: ofplang-run<0.13,>=0.11
23
+ Requires-Dist: ofplang-validate<0.4,>=0.3
24
+ Requires-Dist: ofplang-schedule<0.14,>=0.13
25
+ Requires-Dist: ofplang-run<0.14,>=0.13
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 itself is the `sila2` extra: `pip install labcode[sila2]`,
118
- installed into whichever interpreter runs the scripts.
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 is `pip install labcode[otel]`.
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 schedule` are the ofplang siblings' own CLIs unchanged, so their
137
- options are documented in those repositories. `lc run` is this package's own, and is
138
- described below.
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` is **refused on a refill route** for now: a sila2 script is handed clients,
271
- and which machine's clients a refill should receive — the replenisher's, or both ends' as a
272
- transport may ask for — is not settled. Use `python`.
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 three worked runs: `plate_line`, an
336
- Object-bearing line driven entirely by environment scripts and runnable with no
337
- hardware, and `sila2_seal` and `sila2_plate_cycle`, which drive the reference lab's
338
- SiLA2 servers for real. `sila2_seal` also walks through what a run does when a
339
- machine stops answering — before an operation, and in the middle of one.
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 itself is the `sila2` extra: `pip install labcode[sila2]`,
74
- installed into whichever interpreter runs the scripts.
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 is `pip install labcode[otel]`.
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 schedule` are the ofplang siblings' own CLIs unchanged, so their
93
- options are documented in those repositories. `lc run` is this package's own, and is
94
- described below.
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` is **refused on a refill route** for now: a sila2 script is handed clients,
227
- and which machine's clients a refill should receive — the replenisher's, or both ends' as a
228
- transport may ask for — is not settled. Use `python`.
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 three worked runs: `plate_line`, an
292
- Object-bearing line driven entirely by environment scripts and runnable with no
293
- hardware, and `sila2_seal` and `sila2_plate_cycle`, which drive the reference lab's
294
- SiLA2 servers for real. `sila2_seal` also walks through what a run does when a
295
- machine stops answering — before an operation, and in the middle of one.
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