pybrid-computing 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 (28) hide show
  1. pybrid_computing-0.10.0/PKG-INFO +136 -0
  2. pybrid_computing-0.10.0/README.md +102 -0
  3. pybrid_computing-0.10.0/pybrid/cli/redac/__init__.py +356 -0
  4. pybrid_computing-0.10.0/pybrid/redac/__init__.py +6 -0
  5. pybrid_computing-0.10.0/pybrid/redac/blocks/__init__.py +9 -0
  6. pybrid_computing-0.10.0/pybrid/redac/blocks/block.py +61 -0
  7. pybrid_computing-0.10.0/pybrid/redac/blocks/cblock.py +22 -0
  8. pybrid_computing-0.10.0/pybrid/redac/blocks/iblock.py +38 -0
  9. pybrid_computing-0.10.0/pybrid/redac/blocks/mblock.py +37 -0
  10. pybrid_computing-0.10.0/pybrid/redac/blocks/ublock.py +47 -0
  11. pybrid_computing-0.10.0/pybrid/redac/carrier.py +43 -0
  12. pybrid_computing-0.10.0/pybrid/redac/cluster.py +83 -0
  13. pybrid_computing-0.10.0/pybrid/redac/computations.py +30 -0
  14. pybrid_computing-0.10.0/pybrid/redac/computer.py +42 -0
  15. pybrid_computing-0.10.0/pybrid/redac/controller.py +141 -0
  16. pybrid_computing-0.10.0/pybrid/redac/data.py +64 -0
  17. pybrid_computing-0.10.0/pybrid/redac/display.py +25 -0
  18. pybrid_computing-0.10.0/pybrid/redac/elements.py +28 -0
  19. pybrid_computing-0.10.0/pybrid/redac/entities.py +200 -0
  20. pybrid_computing-0.10.0/pybrid/redac/modules.py +15 -0
  21. pybrid_computing-0.10.0/pybrid/redac/protocol/__init__.py +20 -0
  22. pybrid_computing-0.10.0/pybrid/redac/protocol/envelope.py +62 -0
  23. pybrid_computing-0.10.0/pybrid/redac/protocol/messages.py +726 -0
  24. pybrid_computing-0.10.0/pybrid/redac/protocol/protocol.py +180 -0
  25. pybrid_computing-0.10.0/pybrid/redac/protocol/serializer.py +52 -0
  26. pybrid_computing-0.10.0/pybrid/redac/protocol/types.py +17 -0
  27. pybrid_computing-0.10.0/pybrid/redac/run.py +131 -0
  28. pybrid_computing-0.10.0/pyproject.toml +71 -0
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.1
2
+ Name: pybrid-computing
3
+ Version: 0.10.0
4
+ Summary: Library to interact with analog computers and analog-digital hybrid computers, in particular the LUCIDAC and REDAC computers made by anabrid
5
+ Home-page: https://anabrid.com/
6
+ License: MIT OR GPL-2.0-or-later
7
+ Author: Michael Steck
8
+ Author-email: pybrid-code@anabrid.com
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: Other/Proprietary License
16
+ Classifier: Operating System :: POSIX
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Communications
22
+ Classifier: Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator
23
+ Classifier: Topic :: Software Development :: Embedded Systems
24
+ Classifier: Topic :: Terminals :: Serial
25
+ Requires-Dist: asyncclick (>=8.1.3.4,<9.0.0.0)
26
+ Requires-Dist: inflection (>=0.5.1,<0.6.0)
27
+ Requires-Dist: matplotlib[examples] (>=3.5.3,<4.0.0)
28
+ Requires-Dist: pybrid-computing-base (>=0.10.0,<1)
29
+ Requires-Dist: pydantic (>=1.10.5,<2.0.0)
30
+ Requires-Dist: pyqt5-qt5 (==5.15.2) ; sys_platform == "linux"
31
+ Requires-Dist: pyqt5[examples] (>=5.15.2,<6.0.0)
32
+ Description-Content-Type: text/markdown
33
+
34
+ # pybrid-computing: The python hybrid computing code
35
+
36
+ *pybrid* is a code that provides an interface to configure and control analog computers,
37
+ in particular the network-enabled LUCIDAC/REDAC analog-digital hybrid computers made by
38
+ [anabrid](https://anabrid.com/).
39
+ The abstractions can be used by means of
40
+
41
+ * a fully [asyncio](https://docs.python.org/3/library/asyncio.html) library
42
+ * a framework (users providing callbacks in a clean class structure, however giving
43
+ up most of the program flow control) called *Read-Eval-Configure-Loop* (RECL)
44
+ * a command line interface using [click](https://click.palletsprojects.com)
45
+
46
+ This python library provides the reference implementation for the LUCIDAC/REDAC
47
+ communication protocol (a simple ASCII yet fully asynchronous RPC message passing protocol
48
+ ontop of JSONL) by means of [pydantic](https://docs.pydantic.dev/latest/) data type modeling.
49
+ In particular, it is the reference implementation of a client communicating with a
50
+ [LUCIDAC hybrid controller MCU](https://anabrid.dev/docs/hybrid-controller/) via
51
+ USB Serial or TCP/IP.
52
+
53
+ The code is successfully used on modern day GNU/Linux, Mac OS X and MS Windows.
54
+
55
+ This code is written and maintained by [anabrid GmbH](https://anabrid.com/). It is released
56
+ as open source (dual licensed as `MIT` and `GPL>=2`). It is supposed to be released at Github
57
+ (**TODO**).
58
+
59
+ ## Installation
60
+
61
+ Simply run `pip install pybrid-computing`. There is an explicit dependency to
62
+ `pybrid-computing-base` released and maintained by the same authors, all other dependencies
63
+ are widespread and thus relatively standard python packages.
64
+
65
+ Installation should make a command line executable `pybrid` available on your shell. You can
66
+ explore it by calling `pybrid` on your shell, which gives you, for instance:
67
+
68
+ ```
69
+ % pybrid redac
70
+ Usage: pybrid redac [OPTIONS] COMMAND [ARGS]...
71
+
72
+ Entrypoint for all REDAC commands.
73
+
74
+ Use :code:`pybrid redac --help` to list all available sub-commands.
75
+
76
+ Options:
77
+ -h, --host TEXT Network name or address of the REDAC.
78
+ -p, --port INTEGER Network port of the REDAC.
79
+ --reset / --no-reset Whether to reset the REDAC after connecting.
80
+ [default: reset]
81
+ --help Show this message and exit.
82
+
83
+ Commands:
84
+ display Display the hardware structure of the REDAC.
85
+ get-entity-config Get the configuration of an entity.
86
+ hack Collects 'hack' commands, for development purposes...
87
+ reset Reset the REDAC to initial configuration.
88
+ route Route a signal on one cluster from one output of...
89
+ run Start a run (computation) and wait until it is...
90
+ set-alias Define an alias for a path in an interactive...
91
+ set-connection Set one or multiple connections in a U-Block or...
92
+ set-daq Configure data acquisition of subsequent run commands.
93
+ set-element-config Set one ATTRIBUTE to VALUE of the configuration of...
94
+ shell Start an interactive shell and/or execute a REDAC...
95
+ user-program
96
+ ```
97
+
98
+ ## Documentation
99
+
100
+ > **You can read the documentation online at https://anabrid.dev/docs/pybrid/html/**
101
+
102
+ It is generated by the [Sphinx](https://www.sphinx-doc.org/) documentation which
103
+ is part of this repository/package.
104
+
105
+ ## Getting started as developer
106
+
107
+ This project is managed via the [poetry](https://python-poetry.org/) dependency
108
+ manager for python. It allows for easy dependency installation, etc. You should
109
+ make yourself comfortable with poetry (https://python-poetry.org/docs/basic-usage/).
110
+
111
+ Note that the code is distributed amongst the `pybrid-computing` and `pybrid-computing-base`
112
+ repositories. If you enter `poetry install` while being in one of these repositories with
113
+ your shell, you can work on *that* python package "life". If you want to work on both of
114
+ them, go to the `pybrid-computing` and edit the
115
+ [path dependencies](https://python-poetry.org/docs/dependency-specification/#path-dependencies)
116
+ in the `pyproject.toml`, for instance by replacing the line
117
+
118
+ ```
119
+ ...
120
+ [tool.poetry.dependencies]
121
+ ...
122
+ pybrid-computing-base = "^0, >=0.10.0"
123
+ ...
124
+ ```
125
+
126
+ with something as in
127
+
128
+ ```
129
+ pybrid-computing-base = { path = "/path/to/where/you/checked/out/pybrid-computing-base", develop = true } # instead of "^0, >=0.10.0"
130
+ ```
131
+
132
+ After this edit, run `poetry install` again to update the paths.
133
+
134
+ Some IDEs such as PyCharm can check this out by their own.
135
+
136
+
@@ -0,0 +1,102 @@
1
+ # pybrid-computing: The python hybrid computing code
2
+
3
+ *pybrid* is a code that provides an interface to configure and control analog computers,
4
+ in particular the network-enabled LUCIDAC/REDAC analog-digital hybrid computers made by
5
+ [anabrid](https://anabrid.com/).
6
+ The abstractions can be used by means of
7
+
8
+ * a fully [asyncio](https://docs.python.org/3/library/asyncio.html) library
9
+ * a framework (users providing callbacks in a clean class structure, however giving
10
+ up most of the program flow control) called *Read-Eval-Configure-Loop* (RECL)
11
+ * a command line interface using [click](https://click.palletsprojects.com)
12
+
13
+ This python library provides the reference implementation for the LUCIDAC/REDAC
14
+ communication protocol (a simple ASCII yet fully asynchronous RPC message passing protocol
15
+ ontop of JSONL) by means of [pydantic](https://docs.pydantic.dev/latest/) data type modeling.
16
+ In particular, it is the reference implementation of a client communicating with a
17
+ [LUCIDAC hybrid controller MCU](https://anabrid.dev/docs/hybrid-controller/) via
18
+ USB Serial or TCP/IP.
19
+
20
+ The code is successfully used on modern day GNU/Linux, Mac OS X and MS Windows.
21
+
22
+ This code is written and maintained by [anabrid GmbH](https://anabrid.com/). It is released
23
+ as open source (dual licensed as `MIT` and `GPL>=2`). It is supposed to be released at Github
24
+ (**TODO**).
25
+
26
+ ## Installation
27
+
28
+ Simply run `pip install pybrid-computing`. There is an explicit dependency to
29
+ `pybrid-computing-base` released and maintained by the same authors, all other dependencies
30
+ are widespread and thus relatively standard python packages.
31
+
32
+ Installation should make a command line executable `pybrid` available on your shell. You can
33
+ explore it by calling `pybrid` on your shell, which gives you, for instance:
34
+
35
+ ```
36
+ % pybrid redac
37
+ Usage: pybrid redac [OPTIONS] COMMAND [ARGS]...
38
+
39
+ Entrypoint for all REDAC commands.
40
+
41
+ Use :code:`pybrid redac --help` to list all available sub-commands.
42
+
43
+ Options:
44
+ -h, --host TEXT Network name or address of the REDAC.
45
+ -p, --port INTEGER Network port of the REDAC.
46
+ --reset / --no-reset Whether to reset the REDAC after connecting.
47
+ [default: reset]
48
+ --help Show this message and exit.
49
+
50
+ Commands:
51
+ display Display the hardware structure of the REDAC.
52
+ get-entity-config Get the configuration of an entity.
53
+ hack Collects 'hack' commands, for development purposes...
54
+ reset Reset the REDAC to initial configuration.
55
+ route Route a signal on one cluster from one output of...
56
+ run Start a run (computation) and wait until it is...
57
+ set-alias Define an alias for a path in an interactive...
58
+ set-connection Set one or multiple connections in a U-Block or...
59
+ set-daq Configure data acquisition of subsequent run commands.
60
+ set-element-config Set one ATTRIBUTE to VALUE of the configuration of...
61
+ shell Start an interactive shell and/or execute a REDAC...
62
+ user-program
63
+ ```
64
+
65
+ ## Documentation
66
+
67
+ > **You can read the documentation online at https://anabrid.dev/docs/pybrid/html/**
68
+
69
+ It is generated by the [Sphinx](https://www.sphinx-doc.org/) documentation which
70
+ is part of this repository/package.
71
+
72
+ ## Getting started as developer
73
+
74
+ This project is managed via the [poetry](https://python-poetry.org/) dependency
75
+ manager for python. It allows for easy dependency installation, etc. You should
76
+ make yourself comfortable with poetry (https://python-poetry.org/docs/basic-usage/).
77
+
78
+ Note that the code is distributed amongst the `pybrid-computing` and `pybrid-computing-base`
79
+ repositories. If you enter `poetry install` while being in one of these repositories with
80
+ your shell, you can work on *that* python package "life". If you want to work on both of
81
+ them, go to the `pybrid-computing` and edit the
82
+ [path dependencies](https://python-poetry.org/docs/dependency-specification/#path-dependencies)
83
+ in the `pyproject.toml`, for instance by replacing the line
84
+
85
+ ```
86
+ ...
87
+ [tool.poetry.dependencies]
88
+ ...
89
+ pybrid-computing-base = "^0, >=0.10.0"
90
+ ...
91
+ ```
92
+
93
+ with something as in
94
+
95
+ ```
96
+ pybrid-computing-base = { path = "/path/to/where/you/checked/out/pybrid-computing-base", develop = true } # instead of "^0, >=0.10.0"
97
+ ```
98
+
99
+ After this edit, run `poetry install` again to update the paths.
100
+
101
+ Some IDEs such as PyCharm can check this out by their own.
102
+
@@ -0,0 +1,356 @@
1
+ # Copyright (c) 2022-2024 anabrid GmbH
2
+ # Contact: https://www.anabrid.com/licensing/
3
+ # SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
+
5
+ import logging
6
+
7
+ import asyncclick as click
8
+ from asyncclick import Choice
9
+
10
+ from pybrid.base.hybrid import EntityDoesNotExist
11
+ from pybrid.base.transport import TCPTransport
12
+ from pybrid.cli.base import cli
13
+ from pybrid.cli.base.commands import user_program
14
+ from pybrid.cli.base.ressources import ManagedAsyncResource
15
+ from pybrid.cli.base.shell import Shell
16
+
17
+ from pybrid.redac.blocks import SwitchingBlock
18
+ from pybrid.redac.cluster import Cluster
19
+ from pybrid.redac.controller import Controller
20
+ from pybrid.redac.data import DatExporter
21
+ from pybrid.redac.display import TreeDisplay
22
+ from pybrid.redac.entities import Path, Entity
23
+ from pybrid.redac.protocol.protocol import Protocol
24
+ from pybrid.redac.run import Run, RunState, RunError
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ @cli.group()
30
+ @click.pass_context
31
+ @click.option('--host', '-h', type=str, required=False, help="Network name or address of the REDAC.")
32
+ @click.option('--port', '-p', type=int, default=5732, required=False, help="Network port of the REDAC.")
33
+ @click.option('--reset/--no-reset', is_flag=True, default=True, show_default=True,
34
+ help="Whether to reset the REDAC after connecting.")
35
+ async def redac(ctx: click.Context, host, port, reset):
36
+ """
37
+ Entrypoint for all REDAC commands.
38
+
39
+ Use :code:`pybrid redac --help` to list all available sub-commands.
40
+ """
41
+
42
+ # Generate a transport
43
+ if host is not None and port is not None:
44
+ transport_ = ctx.obj["transport"] = await TCPTransport.create(host, port)
45
+ else:
46
+ raise RuntimeError("No valid combination of transport options given.")
47
+
48
+ # Generate a protocol
49
+ protocol = await Protocol.create(transport_)
50
+
51
+ # Generate a controller, which will also start the protocol
52
+ controller = await Controller.create(protocol)
53
+ ctx.obj["controller"] = await ctx.with_async_resource(ManagedAsyncResource(controller, 'start', 'stop'))
54
+
55
+ # Unless chosen otherwise, reset the analog computer
56
+ if reset:
57
+ await controller.reset()
58
+
59
+ # Create a run which is potentially modified by other commands (e.g. set-readout-elements)
60
+ ctx.obj["run"] = await controller.create_run()
61
+ ctx.obj["previous_run"] = None
62
+
63
+
64
+ @redac.command()
65
+ @click.pass_obj
66
+ @click.argument('path', type=str)
67
+ @click.argument('alias', type=str)
68
+ async def set_alias(obj, path, alias):
69
+ """
70
+ Define an alias for a path in an interactive session or script.
71
+ You can use the alias in subsequent commands instead of a path argument.
72
+
73
+ PATH is the path the alias should resolve to.
74
+ ALIAS is the name of the alias.
75
+
76
+ If '*' is passed for the path as first argument, the alias is set to point
77
+ to the next carrier board which does not yet have an alias set for it.
78
+ """
79
+ controller: Controller = obj["controller"]
80
+ aliases: dict[str, Path] = obj.get("aliases", {})
81
+ # Set alias supports a special '*' path as first argument,
82
+ # in which case it selects the next carrier board which was not yet aliased.
83
+ # This is used to not have to hard-code carrier board identifiers for (simple) examples.
84
+ if path == '*':
85
+ aliased_carrier_paths = {path for path in aliases.values() if path.depth == 1}
86
+ for carrier in controller.computer.carriers:
87
+ if carrier.path not in aliased_carrier_paths:
88
+ path_ = carrier.path
89
+ break
90
+ else:
91
+ raise EntityDoesNotExist("No more carrier boards available.")
92
+ else:
93
+ path_ = Path.parse(path, aliases=aliases)
94
+ # Save alias
95
+ if "aliases" not in obj:
96
+ obj["aliases"] = dict()
97
+ obj["aliases"].update({alias: path_})
98
+
99
+
100
+ @redac.command()
101
+ @click.pass_obj
102
+ async def display(obj):
103
+ """
104
+ Display the hardware structure of the REDAC.
105
+ """
106
+ controller: Controller = obj["controller"]
107
+ click.echo(TreeDisplay().render(controller.computer))
108
+
109
+
110
+ @redac.command()
111
+ @click.pass_obj
112
+ @click.option('--keep-calibration', type=bool, default=True, help='Whether to keep calibration.')
113
+ @click.option('--sync/--no-sync', default=True, help='Whether to immediately sync configuration to hardware.')
114
+ async def reset(obj, keep_calibration, sync):
115
+ """
116
+ Reset the REDAC to initial configuration.
117
+ """
118
+ controller: Controller = obj["controller"]
119
+ await controller.reset(keep_calibration=keep_calibration, sync=sync)
120
+
121
+
122
+ @redac.command()
123
+ @click.pass_obj
124
+ @click.option('-r', '--recursive', type=bool, default=True, help='Whether to get config recursively for sub-entities.')
125
+ @click.argument('path', type=str)
126
+ async def get_entity_config(obj, recursive, path):
127
+ """
128
+ Get the configuration of an entity.
129
+
130
+ PATH is the unique path of the entity.
131
+ """
132
+ controller: Controller = obj["controller"]
133
+
134
+ path_ = Path.parse(path, aliases=obj.get("aliases", None))
135
+ config = await controller.protocol.get_config(path_, recursive)
136
+ click.echo(config)
137
+
138
+
139
+ @redac.command()
140
+ @click.pass_obj
141
+ @click.option('--sync/--no-sync', default=True, help='Whether to immediately send configuration to hybrid controller.')
142
+ @click.argument('path', type=str)
143
+ @click.argument('attribute', type=str)
144
+ @click.argument('value', type=str)
145
+ async def set_element_config(obj, sync, path, attribute, value):
146
+ """
147
+ Set one ATTRIBUTE to VALUE of the configuration of an entity at PATH.
148
+
149
+ PATH is the unique path of the entity.
150
+ ATTRIBUTE is the name of the attribute to change, e.g. 'factor'.
151
+ VALUE is the new value of the attribute, e.g. '0.42'.
152
+ """
153
+ controller: Controller = obj["controller"]
154
+
155
+ path_ = Path.parse(path, aliases=obj.get("aliases", None))
156
+
157
+ # Try to get the entity by its path
158
+ entity: Entity = controller.computer.get_entity(path_)
159
+
160
+ # Apply configuration to element
161
+ entity.apply_partial_configuration(attribute, value)
162
+
163
+ # Build a configuration message to the parent block
164
+ entity_config = entity.generate_partial_configuration(attribute)
165
+
166
+ if sync:
167
+ if path_.depth >= 4:
168
+ await controller.protocol.set_config_request(entity=path_.parent,
169
+ config={"elements": {path_.id_: entity_config}})
170
+ else:
171
+ await controller.protocol.set_config_request(entity=path_, config=entity_config)
172
+
173
+
174
+ @redac.command()
175
+ @click.pass_obj
176
+ @click.option('--sync/--no-sync', default=True, help='Whether to immediately send configuration to hybrid controller.')
177
+ @click.option('--force', is_flag=True, default=False, show_default=True,
178
+ help="Force connection, possibly disconnecting existing connections.")
179
+ @click.argument('path', type=str)
180
+ @click.argument('connections', type=int, nargs=-1)
181
+ async def set_connection(obj, sync, force, path, connections):
182
+ """
183
+ Set one or multiple connections in a U-Block or I-Block.
184
+
185
+ PATH is the unique path to either a U-Block or I-Block.
186
+ CONNECTIONS specifies which connections should be set.
187
+ For a U-Block, the syntax is <input> <output> [<output> ...].
188
+ For a I-Block, the syntax is <input> [<input> ...] <output>.
189
+ """
190
+ controller: Controller = obj["controller"]
191
+
192
+ # Sanity check connections, which must be at least two arguments
193
+ if len(connections) < 2:
194
+ raise ValueError("You must supply at least two arguments for connection specification.")
195
+
196
+ # Try to get the entity by its path
197
+ path_ = Path.parse(path, aliases=obj.get("aliases", None))
198
+ entity = controller.computer.get_entity(path_)
199
+ # It must be a SwitchingBlock
200
+ if not isinstance(entity, SwitchingBlock):
201
+ raise ValueError("Expected a path to a SwitchingBlock.")
202
+
203
+ # Set connection, data structure depends on block type
204
+ entity.connect(*connections, force=force)
205
+
206
+ # Send configuration
207
+ if sync:
208
+ carrier = controller.computer.get_entity(path_.to_carrier())
209
+ await controller.set_config(carrier)
210
+
211
+
212
+ @redac.command()
213
+ @click.pass_obj
214
+ @click.option('--sync/--no-sync', default=True, help='Whether to immediately send configuration to hybrid controller.')
215
+ @click.argument('path', type=str)
216
+ @click.argument('m_out', type=int)
217
+ @click.argument('u_out', type=int)
218
+ @click.argument('c_factor', type=float)
219
+ @click.argument('m_in', type=int)
220
+ async def route(obj, sync, path, m_out, u_out, c_factor, m_in):
221
+ """
222
+ Route a signal on one cluster from one output of one M-Block through the U-Block, a coefficient on the C-Block,
223
+ through the I-Block and back to one input of one M-Block.
224
+
225
+ PATH is the unique path of the entity.
226
+ M_OUT is the M-Block signal output index.
227
+ U_OUT is the U-Block signal output index (equals coefficient index).
228
+ C_FACTOR is the factor of the coefficient.
229
+ M_IN is the M-Block signal input index (equals I-Block signal output index).
230
+ """
231
+ controller: Controller = obj["controller"]
232
+
233
+ # Try to get the entity by its path
234
+ path_ = Path.parse(path, aliases=obj.get("aliases", None))
235
+ cluster = controller.computer.get_entity(path_)
236
+ # It must be a SwitchingBlock
237
+ if not isinstance(cluster, Cluster):
238
+ raise ValueError("Expected a path to a Cluster.")
239
+
240
+ cluster.route(m_out, u_out, c_factor, m_in)
241
+ if sync:
242
+ await controller.set_config(cluster)
243
+
244
+
245
+ @redac.command()
246
+ @click.pass_obj
247
+ @click.option('--sample-rate', '-r', type=Choice(
248
+ ['1', '2', '4', '5', '8', '10', '16', '20', '25', '32', '40', '50', '64', '80', '100', '125', '160', '200', '250',
249
+ '320', '400', '500', '625', '800', '1000', '1250', '1600', '2000', '2500', '3125', '4000', '5000', '6250', '8000',
250
+ '10000', '12500', '15625', '20000', '25000', '31250', '40000', '50000', '62500', '100000', '125000', '200000',
251
+ '250000', '500000', '1000000']), required=False, help="Sample rate in samples/second.")
252
+ @click.option('--num-channels', '-n', type=Choice(['0', '1', '2', '4', '8']), default='0', help="Number of channels.")
253
+ async def set_daq(obj, sample_rate, num_channels):
254
+ """
255
+ Configure data acquisition of subsequent run commands.
256
+ Only useful in interactive sessions or scripts.
257
+ Is lost once the session or script ends.
258
+ """
259
+ controller: Controller = obj["controller"]
260
+ run_: Run = obj["run"]
261
+
262
+ run_.daq.num_channels = num_channels
263
+ if sample_rate is not None:
264
+ run_.daq.sample_rate = int(sample_rate)
265
+
266
+
267
+ @redac.command()
268
+ @click.pass_obj
269
+ # Run options
270
+ @click.option('--op-time', type=int, default=None, help='OP time in nanoseconds.')
271
+ @click.option('--ic-time', type=int, default=None, help='IC time in nanoseconds.')
272
+ # Output options
273
+ @click.option('--output', '-o', type=click.File('wt'), default='-', help="File to write data to.")
274
+ @click.option('--output-format', '-f', type=click.Choice(choices=("none", "dat",)), default="dat",
275
+ help="Format to write data in.")
276
+ async def run(obj, op_time, ic_time, output, output_format):
277
+ """
278
+ Start a run (computation) and wait until it is complete.
279
+ """
280
+ controller: Controller = obj["controller"]
281
+ run_: Run = obj["run"]
282
+
283
+ # If the run in the context object is already done, we need a new one
284
+ if run_.state.is_done():
285
+ run_ = Run.make_from_other_run(run_)
286
+
287
+ # Set run config
288
+ if ic_time is not None:
289
+ run_.config.ic_time = ic_time
290
+ if op_time is not None:
291
+ run_.config.op_time = op_time
292
+
293
+ timeout = max(run_.config.op_time / 1_000_000_000 + 3, 3)
294
+ run_ = obj["run"] = await controller.start_and_await_run(run_, timeout=timeout)
295
+ if run_.state is RunState.ERROR:
296
+ raise RunError("Error while executing run.")
297
+
298
+ if output_format == "dat":
299
+ exporter = DatExporter(output)
300
+ exporter.export(run_)
301
+
302
+
303
+ @redac.command()
304
+ @click.pass_context
305
+ @click.option('--ignore-errors', is_flag=True, default=False, show_default=True,
306
+ help="Ignore errors while executing a script.")
307
+ @click.option('--exit-after-script', '-x', is_flag=True, default=False, show_default=True,
308
+ help="Exit after the scripts have been executed. Useful if output is piped into other programs.")
309
+ @click.argument('scripts', nargs=-1, type=click.File('r'))
310
+ async def shell(ctx: click.Context, ignore_errors, exit_after_script, scripts):
311
+ """
312
+ Start an interactive shell and/or execute a REDAC shell SCRIPT.
313
+
314
+ SCRIPTS is a list of REDAC shell script files to execute before starting the interactive session."
315
+ """
316
+ computer_name = ctx.obj["controller"].computer.name
317
+
318
+ # Create and start a shell
319
+ shell_ = Shell(base_group=redac, base_ctx=ctx.parent, slug=computer_name, prompt=f"{computer_name} >> ")
320
+ with shell_:
321
+ for script in scripts:
322
+ logger.debug("Executing %s.", script.name)
323
+ for line_no, line in enumerate(script):
324
+ line = line.strip()
325
+ if not line or line.startswith('#'):
326
+ continue
327
+ try:
328
+ await shell_.execute_cmdline(line)
329
+ except Exception as exc:
330
+ logger.exception("Error in script during '%s' (line %s): %s", line, line_no, exc)
331
+ if not ignore_errors:
332
+ raise
333
+ if not exit_after_script:
334
+ await shell_.repl_loop()
335
+
336
+
337
+ @redac.group()
338
+ async def hack():
339
+ """
340
+ Collects 'hack' commands, for development purposes only.
341
+ """
342
+ pass
343
+
344
+
345
+ @hack.command()
346
+ @click.pass_obj
347
+ async def make_slave(obj):
348
+ """
349
+ Set one hybrid controller into slave mode.
350
+ For development purposes only.
351
+ """
352
+ controller: Controller = obj["controller"]
353
+ await controller.hack("slave", True)
354
+
355
+
356
+ redac.command()(user_program)
@@ -0,0 +1,6 @@
1
+ from .computer import REDAC
2
+ from .controller import Controller
3
+ from .elements import ComputationElement
4
+ from .entities import Path
5
+ from .protocol.protocol import Protocol
6
+ from .run import Run, RunConfig, RunFlags, RunState, DAQConfig
@@ -0,0 +1,9 @@
1
+ # Copyright (c) 2022-2024 anabrid GmbH
2
+ # Contact: https://www.anabrid.com/licensing/
3
+ # SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
+
5
+ from .block import FunctionBlock, ElementBlock, SwitchingBlock
6
+ from .cblock import CBlock
7
+ from .iblock import IBlock
8
+ from .mblock import MBlock, MIntBlock
9
+ from .ublock import UBlock
@@ -0,0 +1,61 @@
1
+ # Copyright (c) 2022-2024 anabrid GmbH
2
+ # Contact: https://www.anabrid.com/licensing/
3
+ # SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
+
5
+ import typing
6
+ from dataclasses import dataclass
7
+
8
+ from ..entities import Entity, EntityType
9
+ from ..elements import ComputationElement
10
+
11
+
12
+ class FunctionBlock(Entity):
13
+ @classmethod
14
+ def create_from_entity_type_tree(cls, sub_path, sub_tree):
15
+ # TODO: Refactor out common code
16
+ # Check information on self
17
+ this_entity_type = EntityType.pop_from_dict(sub_tree)
18
+
19
+ # Generate type-specific entity
20
+ entity_class = EntityType.lookup(this_entity_type, decay=True)
21
+ return entity_class(path=sub_path)
22
+
23
+
24
+ @dataclass(kw_only=True)
25
+ class ElementBlock(FunctionBlock):
26
+ """
27
+ Base class for function blocks in a REDAC.
28
+ """
29
+ ELEMENTS: typing.ClassVar[list[typing.Type[ComputationElement]]] = None
30
+ elements: typing.Optional[list[ComputationElement]] = None
31
+
32
+ @property
33
+ def children(self):
34
+ if not self.elements:
35
+ return
36
+ yield from self.elements
37
+
38
+ def __post_init__(self):
39
+ super().__post_init__()
40
+ if self.elements is None:
41
+ self.elements = self.initialize_elements(self.path)
42
+
43
+ @classmethod
44
+ def initialize_elements(cls, base_path) -> list[ComputationElement]:
45
+ if not cls.ELEMENTS:
46
+ return []
47
+ elements: list[ComputationElement] = list(
48
+ E(path=base_path / idx)
49
+ for idx, E in enumerate(cls.ELEMENTS)
50
+ )
51
+ return elements
52
+
53
+
54
+ class SignalConnectionError(Exception):
55
+ pass
56
+
57
+
58
+ @dataclass
59
+ class SwitchingBlock(FunctionBlock):
60
+ def connect(self, *connections, force=False):
61
+ raise NotImplementedError