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 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
@@ -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
+ ]