muxit 0.1.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.
- muxit-0.1.0/.gitignore +99 -0
- muxit-0.1.0/CHANGELOG.md +25 -0
- muxit-0.1.0/LICENSE +21 -0
- muxit-0.1.0/PKG-INFO +165 -0
- muxit-0.1.0/README.md +141 -0
- muxit-0.1.0/pyproject.toml +44 -0
- muxit-0.1.0/src/muxit/__init__.py +46 -0
- muxit-0.1.0/src/muxit/__main__.py +286 -0
- muxit-0.1.0/src/muxit/_version.py +1 -0
- muxit-0.1.0/src/muxit/client.py +594 -0
- muxit-0.1.0/src/muxit/connector.py +206 -0
- muxit-0.1.0/src/muxit/errors.py +99 -0
- muxit-0.1.0/tests/e2e/test_live.py +185 -0
- muxit-0.1.0/tests/unit/conftest.py +130 -0
- muxit-0.1.0/tests/unit/test_client.py +236 -0
- muxit-0.1.0/tests/unit/test_connector.py +114 -0
muxit-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
## .NET build output
|
|
2
|
+
MuxitServer/bin/
|
|
3
|
+
MuxitServer/obj/
|
|
4
|
+
MuxitServer.Tests/bin/
|
|
5
|
+
MuxitServer.Tests/obj/
|
|
6
|
+
drivers/**/bin/
|
|
7
|
+
drivers/**/obj/
|
|
8
|
+
# Vendored third-party SDKs ship source, not binaries. ~130 .dll/.pdb/.exe files
|
|
9
|
+
# under references/fairino-sdk were tracked (the only tracked build output in the
|
|
10
|
+
# repo, most of its 15 MB) until they were untracked.
|
|
11
|
+
references/**/bin/
|
|
12
|
+
references/**/obj/
|
|
13
|
+
tools/**/bin/
|
|
14
|
+
tools/**/obj/
|
|
15
|
+
|
|
16
|
+
## Web UI build output (generated by `node build.js ui`)
|
|
17
|
+
MuxitServer/wwwroot/assets/
|
|
18
|
+
MuxitServer/wwwroot/index.html
|
|
19
|
+
MuxitServer/wwwroot/logs.html
|
|
20
|
+
MuxitServer/wwwroot/logs.js
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## Docs site build output (generated by `node build.js docs`)
|
|
24
|
+
MuxitServer/wwwroot/docs/
|
|
25
|
+
|
|
26
|
+
## Node.js
|
|
27
|
+
node_modules/
|
|
28
|
+
web-ui/node_modules/
|
|
29
|
+
docs-site/node_modules/
|
|
30
|
+
docs-site/.vitepress/cache/
|
|
31
|
+
docs-site/.vitepress/dist/
|
|
32
|
+
# Astro's generated content/type cache. Six of these files were tracked until
|
|
33
|
+
# they were untracked; `astro dev` and `astro build` rewrite them every run.
|
|
34
|
+
website/.astro/
|
|
35
|
+
website/dist/
|
|
36
|
+
|
|
37
|
+
# Lockfiles are committed. Every npm project here has one tracked, so `npm ci`
|
|
38
|
+
# is reproducible everywhere. There is deliberately no ignore rule: the old one
|
|
39
|
+
# ignored package-lock.json globally with a single `!tests/` exception while five
|
|
40
|
+
# of them were force-added anyway, so the rule and the repo disagreed.
|
|
41
|
+
|
|
42
|
+
## NuGet
|
|
43
|
+
*.nupkg
|
|
44
|
+
*.snupkg
|
|
45
|
+
|
|
46
|
+
## Visual Studio
|
|
47
|
+
*.suo
|
|
48
|
+
*.user
|
|
49
|
+
*.userosscache
|
|
50
|
+
*.sln.docstates
|
|
51
|
+
.vs/
|
|
52
|
+
|
|
53
|
+
## Dev license keys (NEVER commit — devkeys.txt was tracked until it was removed)
|
|
54
|
+
devkeys*
|
|
55
|
+
|
|
56
|
+
## Driver signing keys (NEVER commit private keys)
|
|
57
|
+
muxit-driver-private.xml
|
|
58
|
+
*-private.xml
|
|
59
|
+
*-private.pem
|
|
60
|
+
|
|
61
|
+
## Local secrets for the dev console (Lemon Squeezy API key, etc.)
|
|
62
|
+
dev-console/.dev-keys.json
|
|
63
|
+
|
|
64
|
+
## Dev console caches (e.g. cloned driver-registry for the publish wizard)
|
|
65
|
+
.cache/
|
|
66
|
+
|
|
67
|
+
## Release testing
|
|
68
|
+
publish/
|
|
69
|
+
test-install/
|
|
70
|
+
|
|
71
|
+
## Build output (driver packages)
|
|
72
|
+
dist/
|
|
73
|
+
|
|
74
|
+
## Development workspace (runtime only, not tracked — use workspace-template/ for examples)
|
|
75
|
+
workspace/
|
|
76
|
+
|
|
77
|
+
## OS files
|
|
78
|
+
Thumbs.db
|
|
79
|
+
desktop.ini
|
|
80
|
+
.DS_Store
|
|
81
|
+
|
|
82
|
+
.dev.vars
|
|
83
|
+
|
|
84
|
+
## Python (driver SDK + Tier 2 drivers)
|
|
85
|
+
__pycache__/
|
|
86
|
+
*.pyc
|
|
87
|
+
*.pyo
|
|
88
|
+
# `pip install -e drivers/py` (documented in docs-site/reference/driver-sdk-python.md)
|
|
89
|
+
*.egg-info/
|
|
90
|
+
# Per-driver venvs the host creates from a driver's requirements.txt
|
|
91
|
+
drivers/py/*/.venv/
|
|
92
|
+
|
|
93
|
+
## Claude Code — personal overrides (never commit)
|
|
94
|
+
.claude/settings.local.json
|
|
95
|
+
|
|
96
|
+
*.csproj.lscache
|
|
97
|
+
|
|
98
|
+
# Simulated license tier for local dev (node muxit.js → t)
|
|
99
|
+
.muxit-dev.json
|
muxit-0.1.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog — muxit (Python client)
|
|
2
|
+
|
|
3
|
+
The Python client versions on its own, separately from Muxit itself. Changes to the
|
|
4
|
+
server that it depends on are in Muxit's revision history.
|
|
5
|
+
|
|
6
|
+
## 0.1.0 — unreleased
|
|
7
|
+
|
|
8
|
+
First preview.
|
|
9
|
+
|
|
10
|
+
- `muxit.connect()` with host, port, password and token, each falling back to
|
|
11
|
+
`MUXIT_HOST` / `MUXIT_PORT` / `MUXIT_PASSWORD` / `MUXIT_TOKEN` / `MUXIT_TIMEOUT`.
|
|
12
|
+
On the Muxit computer itself the startup token is found automatically from Muxit's
|
|
13
|
+
workspace catalog.
|
|
14
|
+
- Connectors as objects: `psu.voltage = 3`, `psu.voltage`, `psu.reset()`; explicit
|
|
15
|
+
`read` / `write` / `call`; snake_case aliases; tab completion and a property table in
|
|
16
|
+
Jupyter; spelling suggestions; read-only writes refused before anything is sent.
|
|
17
|
+
- `muxit.SimulatedWarning` when the Observe safety level simulated a write or action;
|
|
18
|
+
calls that wait for a confirmation in the Muxit UI print a notice and do not time out.
|
|
19
|
+
- Exceptions with the server's error code and a hint: `ConnectionFailed`,
|
|
20
|
+
`AuthenticationFailed`, `NotFound` / `UnknownName`, `ReadOnly`, `SafetyBlocked`,
|
|
21
|
+
`LimitReached`, `Timeout`.
|
|
22
|
+
- `python -m muxit doctor | list | get | set | call | demo`.
|
|
23
|
+
- Works with any Muxit that serves the WebSocket API. The simulated warning, the
|
|
24
|
+
confirmation notice and the `client:python-…` label in the audit log need the first
|
|
25
|
+
Muxit release after 0.45.0; an older server simply sends none of them.
|
muxit-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Muxit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
muxit-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: muxit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Drive the instruments you set up in Muxit from Python and Jupyter: psu.voltage = 3.
|
|
5
|
+
Project-URL: Homepage, https://muxit.io
|
|
6
|
+
Project-URL: Documentation, https://docs.muxit.io/guides/python-client
|
|
7
|
+
Author: Muxit
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: instrument control,jupyter,lab automation,muxit,scpi,test and measurement
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator
|
|
18
|
+
Classifier: Topic :: System :: Hardware
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Requires-Dist: websockets>=12
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: pytest>=7; extra == 'test'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# muxit — Muxit for Python
|
|
26
|
+
|
|
27
|
+
Set up your instruments once in [Muxit](https://muxit.io). Use them from Python, the dashboard
|
|
28
|
+
and AI at the same time.
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import muxit
|
|
32
|
+
|
|
33
|
+
m = muxit.connect() # Muxit on this computer
|
|
34
|
+
psu = m.connector("psu")
|
|
35
|
+
|
|
36
|
+
psu.voltage = 3.0 # write a property
|
|
37
|
+
print(psu.voltage) # read it back from the device
|
|
38
|
+
psu.reset() # call an action
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
No VISA install, no SCPI strings, no COM ports on the machine that runs Python: the
|
|
42
|
+
connection details live in the Muxit connector. The client talks only to the Muxit server you
|
|
43
|
+
point it at. It sends nothing anywhere else.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install muxit
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Python 3.9 or newer. The only dependency is [`websockets`](https://pypi.org/project/websockets/).
|
|
52
|
+
You also need Muxit running, on this computer or another one on your network.
|
|
53
|
+
|
|
54
|
+
## Check the connection first
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
python -m muxit doctor
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
It shows where the client looks for Muxit, whether Muxit answers, the license tier, the
|
|
61
|
+
safety level and the connectors. It ends with the line to paste into Python. `python -m
|
|
62
|
+
muxit demo` reads and writes the built-in test device, so it works without hardware.
|
|
63
|
+
|
|
64
|
+
## Connecting
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
m = muxit.connect() # 127.0.0.1:8765
|
|
68
|
+
m = muxit.connect("192.168.1.20") # another computer, default port
|
|
69
|
+
m = muxit.connect("192.168.1.20", port=9000)
|
|
70
|
+
m = muxit.connect("192.168.1.20", password="...") # Muxit with remote access on
|
|
71
|
+
m = muxit.connect("ws://lab-pc.local:8765/ws") # a full URL works too
|
|
72
|
+
|
|
73
|
+
with muxit.connect() as m: # closes the connection at the end
|
|
74
|
+
...
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Each setting comes from the argument first, then the environment variable, then the
|
|
78
|
+
default:
|
|
79
|
+
|
|
80
|
+
| Setting | Argument | Environment | Default |
|
|
81
|
+
|----------|------------|------------------|-------------|
|
|
82
|
+
| Host | 1st / `host` | `MUXIT_HOST` | `127.0.0.1` |
|
|
83
|
+
| Port | `port` | `MUXIT_PORT` | `8765` |
|
|
84
|
+
| Password | `password` | `MUXIT_PASSWORD` | — |
|
|
85
|
+
| Token | `token` | `MUXIT_TOKEN` | — |
|
|
86
|
+
| Timeout | `timeout` | `MUXIT_TIMEOUT` | `10` s |
|
|
87
|
+
|
|
88
|
+
With the environment variables set, a notebook holds no address or password and can be
|
|
89
|
+
shared. No password is needed on the Muxit computer itself. From another computer, Muxit
|
|
90
|
+
needs remote access on and a password set (Settings › Remote access).
|
|
91
|
+
|
|
92
|
+
## Connectors, properties and actions
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
m.connectors() # ['psu', 'dmm', 'test-device']
|
|
96
|
+
|
|
97
|
+
psu = m.connector("psu")
|
|
98
|
+
psu = m["bk-psu"] # a name with a dash
|
|
99
|
+
psu = m.bk_psu # ...or with the dash as underscore
|
|
100
|
+
|
|
101
|
+
psu.voltage = 3.0 # write
|
|
102
|
+
v = psu.voltage # read (Muxit's latest polled value)
|
|
103
|
+
psu.output = True
|
|
104
|
+
psu.reset() # action
|
|
105
|
+
robot.moveAbsolute([100, 200, 50])
|
|
106
|
+
robot.move_absolute([100, 200, 50]) # snake_case works when the name is free
|
|
107
|
+
|
|
108
|
+
psu.read("voltage") # explicit forms, for names in a variable
|
|
109
|
+
psu.write("voltage", 3.0)
|
|
110
|
+
psu.call("moveAbsolute", [100, 200, 50])
|
|
111
|
+
|
|
112
|
+
print(psu.describe()) # every property and action, with units
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The client fetches each connector's list of properties and actions on first use. So
|
|
116
|
+
`psu.voltge` fails at once with `Did you mean 'voltage'?`, writing a read-only property is
|
|
117
|
+
refused before anything is sent, and Tab completes names in Jupyter.
|
|
118
|
+
|
|
119
|
+
**Reads are Muxit's latest value.** Muxit polls the properties of a driver
|
|
120
|
+
continuously, every 200 ms by default (`polling.connectorRate`). Straight after a write, a
|
|
121
|
+
read can still show the old value for one poll interval. In a sweep, wait for the device
|
|
122
|
+
to settle (`time.sleep(...)`) before you read, as you would anyway. Properties that the
|
|
123
|
+
connector file defines itself are read live.
|
|
124
|
+
|
|
125
|
+
Values come back as plain Python: numbers, `True`/`False`, strings, lists and dicts. numpy
|
|
126
|
+
values can be sent directly.
|
|
127
|
+
|
|
128
|
+
## When something goes wrong
|
|
129
|
+
|
|
130
|
+
Everything raised is a `muxit.MuxitError` with a message that says what to do.
|
|
131
|
+
`.code` holds the server's error code.
|
|
132
|
+
|
|
133
|
+
| Exception | When |
|
|
134
|
+
|-----------|------|
|
|
135
|
+
| `muxit.ConnectionFailed` | Muxit is not reachable, or the connection dropped |
|
|
136
|
+
| `muxit.AuthenticationFailed` | wrong or missing password or token |
|
|
137
|
+
| `muxit.NotFound` | no such connector, property or action (also an `AttributeError`) |
|
|
138
|
+
| `muxit.ReadOnly` | writing a property that can only be read |
|
|
139
|
+
| `muxit.SafetyBlocked` | Muxit's safety gate refused the write or action |
|
|
140
|
+
| `muxit.LimitReached` | a license limit or the per-connection call rate |
|
|
141
|
+
| `muxit.Timeout` | no answer within the timeout |
|
|
142
|
+
|
|
143
|
+
**`muxit.SimulatedWarning`.** On the *Observe* safety level Muxit simulates writes and
|
|
144
|
+
actions instead of sending them. A new Free installation starts on Observe. The client
|
|
145
|
+
then warns that nothing reached the device. Pick another level with the safety chip in the
|
|
146
|
+
Muxit window.
|
|
147
|
+
|
|
148
|
+
**Confirmations.** If the connector asks for a confirmation before a write, the call waits
|
|
149
|
+
for someone to click it in the Muxit window. While it waits it prints `Waiting for
|
|
150
|
+
confirmation in the Muxit UI`, and it does not time out.
|
|
151
|
+
|
|
152
|
+
## Good to know
|
|
153
|
+
|
|
154
|
+
- Every read and write is a round trip to the server, about a millisecond locally plus the
|
|
155
|
+
device's own time. That is fine for setpoints and measurements, but not for kHz control
|
|
156
|
+
loops.
|
|
157
|
+
- Muxit allows 50 calls per second per connection by default
|
|
158
|
+
(`security.rateLimit.callsPerSec`).
|
|
159
|
+
- Stopping scripts in the Muxit window does not stop your Python program. Use Ctrl+C.
|
|
160
|
+
- Free runs 3 connectors. Per-connector safety limits (`safety.limits`) are a Lab feature.
|
|
161
|
+
They apply to calls from Python too.
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
MIT. See [`LICENSE`](LICENSE). Muxit itself is licensed separately.
|
muxit-0.1.0/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# muxit — Muxit for Python
|
|
2
|
+
|
|
3
|
+
Set up your instruments once in [Muxit](https://muxit.io). Use them from Python, the dashboard
|
|
4
|
+
and AI at the same time.
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
import muxit
|
|
8
|
+
|
|
9
|
+
m = muxit.connect() # Muxit on this computer
|
|
10
|
+
psu = m.connector("psu")
|
|
11
|
+
|
|
12
|
+
psu.voltage = 3.0 # write a property
|
|
13
|
+
print(psu.voltage) # read it back from the device
|
|
14
|
+
psu.reset() # call an action
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
No VISA install, no SCPI strings, no COM ports on the machine that runs Python: the
|
|
18
|
+
connection details live in the Muxit connector. The client talks only to the Muxit server you
|
|
19
|
+
point it at. It sends nothing anywhere else.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install muxit
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Python 3.9 or newer. The only dependency is [`websockets`](https://pypi.org/project/websockets/).
|
|
28
|
+
You also need Muxit running, on this computer or another one on your network.
|
|
29
|
+
|
|
30
|
+
## Check the connection first
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
python -m muxit doctor
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
It shows where the client looks for Muxit, whether Muxit answers, the license tier, the
|
|
37
|
+
safety level and the connectors. It ends with the line to paste into Python. `python -m
|
|
38
|
+
muxit demo` reads and writes the built-in test device, so it works without hardware.
|
|
39
|
+
|
|
40
|
+
## Connecting
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
m = muxit.connect() # 127.0.0.1:8765
|
|
44
|
+
m = muxit.connect("192.168.1.20") # another computer, default port
|
|
45
|
+
m = muxit.connect("192.168.1.20", port=9000)
|
|
46
|
+
m = muxit.connect("192.168.1.20", password="...") # Muxit with remote access on
|
|
47
|
+
m = muxit.connect("ws://lab-pc.local:8765/ws") # a full URL works too
|
|
48
|
+
|
|
49
|
+
with muxit.connect() as m: # closes the connection at the end
|
|
50
|
+
...
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Each setting comes from the argument first, then the environment variable, then the
|
|
54
|
+
default:
|
|
55
|
+
|
|
56
|
+
| Setting | Argument | Environment | Default |
|
|
57
|
+
|----------|------------|------------------|-------------|
|
|
58
|
+
| Host | 1st / `host` | `MUXIT_HOST` | `127.0.0.1` |
|
|
59
|
+
| Port | `port` | `MUXIT_PORT` | `8765` |
|
|
60
|
+
| Password | `password` | `MUXIT_PASSWORD` | — |
|
|
61
|
+
| Token | `token` | `MUXIT_TOKEN` | — |
|
|
62
|
+
| Timeout | `timeout` | `MUXIT_TIMEOUT` | `10` s |
|
|
63
|
+
|
|
64
|
+
With the environment variables set, a notebook holds no address or password and can be
|
|
65
|
+
shared. No password is needed on the Muxit computer itself. From another computer, Muxit
|
|
66
|
+
needs remote access on and a password set (Settings › Remote access).
|
|
67
|
+
|
|
68
|
+
## Connectors, properties and actions
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
m.connectors() # ['psu', 'dmm', 'test-device']
|
|
72
|
+
|
|
73
|
+
psu = m.connector("psu")
|
|
74
|
+
psu = m["bk-psu"] # a name with a dash
|
|
75
|
+
psu = m.bk_psu # ...or with the dash as underscore
|
|
76
|
+
|
|
77
|
+
psu.voltage = 3.0 # write
|
|
78
|
+
v = psu.voltage # read (Muxit's latest polled value)
|
|
79
|
+
psu.output = True
|
|
80
|
+
psu.reset() # action
|
|
81
|
+
robot.moveAbsolute([100, 200, 50])
|
|
82
|
+
robot.move_absolute([100, 200, 50]) # snake_case works when the name is free
|
|
83
|
+
|
|
84
|
+
psu.read("voltage") # explicit forms, for names in a variable
|
|
85
|
+
psu.write("voltage", 3.0)
|
|
86
|
+
psu.call("moveAbsolute", [100, 200, 50])
|
|
87
|
+
|
|
88
|
+
print(psu.describe()) # every property and action, with units
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The client fetches each connector's list of properties and actions on first use. So
|
|
92
|
+
`psu.voltge` fails at once with `Did you mean 'voltage'?`, writing a read-only property is
|
|
93
|
+
refused before anything is sent, and Tab completes names in Jupyter.
|
|
94
|
+
|
|
95
|
+
**Reads are Muxit's latest value.** Muxit polls the properties of a driver
|
|
96
|
+
continuously, every 200 ms by default (`polling.connectorRate`). Straight after a write, a
|
|
97
|
+
read can still show the old value for one poll interval. In a sweep, wait for the device
|
|
98
|
+
to settle (`time.sleep(...)`) before you read, as you would anyway. Properties that the
|
|
99
|
+
connector file defines itself are read live.
|
|
100
|
+
|
|
101
|
+
Values come back as plain Python: numbers, `True`/`False`, strings, lists and dicts. numpy
|
|
102
|
+
values can be sent directly.
|
|
103
|
+
|
|
104
|
+
## When something goes wrong
|
|
105
|
+
|
|
106
|
+
Everything raised is a `muxit.MuxitError` with a message that says what to do.
|
|
107
|
+
`.code` holds the server's error code.
|
|
108
|
+
|
|
109
|
+
| Exception | When |
|
|
110
|
+
|-----------|------|
|
|
111
|
+
| `muxit.ConnectionFailed` | Muxit is not reachable, or the connection dropped |
|
|
112
|
+
| `muxit.AuthenticationFailed` | wrong or missing password or token |
|
|
113
|
+
| `muxit.NotFound` | no such connector, property or action (also an `AttributeError`) |
|
|
114
|
+
| `muxit.ReadOnly` | writing a property that can only be read |
|
|
115
|
+
| `muxit.SafetyBlocked` | Muxit's safety gate refused the write or action |
|
|
116
|
+
| `muxit.LimitReached` | a license limit or the per-connection call rate |
|
|
117
|
+
| `muxit.Timeout` | no answer within the timeout |
|
|
118
|
+
|
|
119
|
+
**`muxit.SimulatedWarning`.** On the *Observe* safety level Muxit simulates writes and
|
|
120
|
+
actions instead of sending them. A new Free installation starts on Observe. The client
|
|
121
|
+
then warns that nothing reached the device. Pick another level with the safety chip in the
|
|
122
|
+
Muxit window.
|
|
123
|
+
|
|
124
|
+
**Confirmations.** If the connector asks for a confirmation before a write, the call waits
|
|
125
|
+
for someone to click it in the Muxit window. While it waits it prints `Waiting for
|
|
126
|
+
confirmation in the Muxit UI`, and it does not time out.
|
|
127
|
+
|
|
128
|
+
## Good to know
|
|
129
|
+
|
|
130
|
+
- Every read and write is a round trip to the server, about a millisecond locally plus the
|
|
131
|
+
device's own time. That is fine for setpoints and measurements, but not for kHz control
|
|
132
|
+
loops.
|
|
133
|
+
- Muxit allows 50 calls per second per connection by default
|
|
134
|
+
(`security.rateLimit.callsPerSec`).
|
|
135
|
+
- Stopping scripts in the Muxit window does not stop your Python program. Use Ctrl+C.
|
|
136
|
+
- Free runs 3 connectors. Per-connector safety limits (`safety.limits`) are a Lab feature.
|
|
137
|
+
They apply to calls from Python too.
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT. See [`LICENSE`](LICENSE). Muxit itself is licensed separately.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "muxit"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Drive the instruments you set up in Muxit from Python and Jupyter: psu.voltage = 3."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
authors = [{ name = "Muxit" }]
|
|
14
|
+
keywords = ["lab automation", "instrument control", "scpi", "test and measurement", "jupyter", "muxit"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Topic :: Scientific/Engineering",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator",
|
|
23
|
+
"Topic :: System :: Hardware",
|
|
24
|
+
]
|
|
25
|
+
dependencies = ["websockets>=12"]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
test = ["pytest>=7"]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://muxit.io"
|
|
32
|
+
Documentation = "https://docs.muxit.io/guides/python-client"
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
muxit = "muxit.__main__:main"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.version]
|
|
38
|
+
path = "src/muxit/_version.py"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["src/muxit"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests/unit"]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Drive the instruments you set up in Muxit from Python.
|
|
2
|
+
|
|
3
|
+
import muxit
|
|
4
|
+
|
|
5
|
+
m = muxit.connect() # Muxit on this computer
|
|
6
|
+
psu = m.connector("psu")
|
|
7
|
+
psu.voltage = 3.0 # write
|
|
8
|
+
print(psu.voltage) # read (Muxit's latest polled value)
|
|
9
|
+
psu.reset() # action
|
|
10
|
+
|
|
11
|
+
Docs: https://docs.muxit.io/guides/python-client
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from ._version import __version__
|
|
15
|
+
from .client import Muxit, Settings, connect
|
|
16
|
+
from .connector import Connector
|
|
17
|
+
from .errors import (
|
|
18
|
+
AuthenticationFailed,
|
|
19
|
+
ConnectionFailed,
|
|
20
|
+
LimitReached,
|
|
21
|
+
MuxitError,
|
|
22
|
+
NotFound,
|
|
23
|
+
ReadOnly,
|
|
24
|
+
SafetyBlocked,
|
|
25
|
+
SimulatedWarning,
|
|
26
|
+
Timeout,
|
|
27
|
+
UnknownName,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"__version__",
|
|
32
|
+
"connect",
|
|
33
|
+
"Muxit",
|
|
34
|
+
"Connector",
|
|
35
|
+
"Settings",
|
|
36
|
+
"MuxitError",
|
|
37
|
+
"ConnectionFailed",
|
|
38
|
+
"AuthenticationFailed",
|
|
39
|
+
"NotFound",
|
|
40
|
+
"UnknownName",
|
|
41
|
+
"ReadOnly",
|
|
42
|
+
"SafetyBlocked",
|
|
43
|
+
"LimitReached",
|
|
44
|
+
"Timeout",
|
|
45
|
+
"SimulatedWarning",
|
|
46
|
+
]
|