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.
- pybrid_computing-0.10.0/PKG-INFO +136 -0
- pybrid_computing-0.10.0/README.md +102 -0
- pybrid_computing-0.10.0/pybrid/cli/redac/__init__.py +356 -0
- pybrid_computing-0.10.0/pybrid/redac/__init__.py +6 -0
- pybrid_computing-0.10.0/pybrid/redac/blocks/__init__.py +9 -0
- pybrid_computing-0.10.0/pybrid/redac/blocks/block.py +61 -0
- pybrid_computing-0.10.0/pybrid/redac/blocks/cblock.py +22 -0
- pybrid_computing-0.10.0/pybrid/redac/blocks/iblock.py +38 -0
- pybrid_computing-0.10.0/pybrid/redac/blocks/mblock.py +37 -0
- pybrid_computing-0.10.0/pybrid/redac/blocks/ublock.py +47 -0
- pybrid_computing-0.10.0/pybrid/redac/carrier.py +43 -0
- pybrid_computing-0.10.0/pybrid/redac/cluster.py +83 -0
- pybrid_computing-0.10.0/pybrid/redac/computations.py +30 -0
- pybrid_computing-0.10.0/pybrid/redac/computer.py +42 -0
- pybrid_computing-0.10.0/pybrid/redac/controller.py +141 -0
- pybrid_computing-0.10.0/pybrid/redac/data.py +64 -0
- pybrid_computing-0.10.0/pybrid/redac/display.py +25 -0
- pybrid_computing-0.10.0/pybrid/redac/elements.py +28 -0
- pybrid_computing-0.10.0/pybrid/redac/entities.py +200 -0
- pybrid_computing-0.10.0/pybrid/redac/modules.py +15 -0
- pybrid_computing-0.10.0/pybrid/redac/protocol/__init__.py +20 -0
- pybrid_computing-0.10.0/pybrid/redac/protocol/envelope.py +62 -0
- pybrid_computing-0.10.0/pybrid/redac/protocol/messages.py +726 -0
- pybrid_computing-0.10.0/pybrid/redac/protocol/protocol.py +180 -0
- pybrid_computing-0.10.0/pybrid/redac/protocol/serializer.py +52 -0
- pybrid_computing-0.10.0/pybrid/redac/protocol/types.py +17 -0
- pybrid_computing-0.10.0/pybrid/redac/run.py +131 -0
- 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,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
|