lockwire 0.2.0__tar.gz → 0.2.3__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.
- {lockwire-0.2.0 → lockwire-0.2.3}/PKG-INFO +139 -29
- {lockwire-0.2.0 → lockwire-0.2.3}/README.md +137 -27
- {lockwire-0.2.0 → lockwire-0.2.3}/pyproject.toml +2 -2
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/__init__.py +1 -1
- lockwire-0.2.3/src/lockwire/_filelock.py +38 -0
- lockwire-0.2.3/src/lockwire/advice.py +215 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/api_payload.py +59 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/bench.py +3 -1
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/catalog_seed.yaml +233 -6
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/checks.py +106 -13
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/cli.py +43 -12
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/copy.py +58 -10
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/describe.py +158 -46
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/edit.py +7 -4
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/emit_ts.py +5 -1
- lockwire-0.2.3/src/lockwire/examples/arm/LICENSE +201 -0
- lockwire-0.2.3/src/lockwire/examples/arm/NOTICE +5 -0
- lockwire-0.2.3/src/lockwire/examples/arm/contract.yaml +299 -0
- lockwire-0.2.3/src/lockwire/examples/arm/plan.yaml +9 -0
- lockwire-0.2.3/src/lockwire/examples/arm/scene.glb +0 -0
- lockwire-0.2.3/src/lockwire/examples/arm/starter.json +7 -0
- lockwire-0.2.3/src/lockwire/examples/drone/contract.yaml +617 -0
- lockwire-0.2.3/src/lockwire/examples/drone/plan.yaml +10 -0
- lockwire-0.2.3/src/lockwire/examples/drone/scene.glb +0 -0
- lockwire-0.2.3/src/lockwire/examples/drone/starter.json +7 -0
- lockwire-0.2.3/src/lockwire/examples/humanoid/NOTICE +26 -0
- lockwire-0.2.3/src/lockwire/examples/humanoid/contract.yaml +1593 -0
- lockwire-0.2.3/src/lockwire/examples/humanoid/plan.yaml +10 -0
- lockwire-0.2.3/src/lockwire/examples/humanoid/scene.glb +0 -0
- lockwire-0.2.3/src/lockwire/examples/humanoid/starter.json +1 -0
- lockwire-0.2.3/src/lockwire/examples/quadruped/NOTICE +32 -0
- lockwire-0.2.3/src/lockwire/examples/quadruped/contract.yaml +476 -0
- lockwire-0.2.3/src/lockwire/examples/quadruped/plan.yaml +5 -0
- lockwire-0.2.3/src/lockwire/examples/quadruped/scene.glb +0 -0
- lockwire-0.2.3/src/lockwire/examples/quadruped/starter.json +7 -0
- lockwire-0.2.3/src/lockwire/examples/rover/NOTICE +12 -0
- lockwire-0.2.3/src/lockwire/examples/rover/contract.yaml +315 -0
- lockwire-0.2.3/src/lockwire/examples/rover/plan.yaml +9 -0
- lockwire-0.2.3/src/lockwire/examples/rover/scene.glb +0 -0
- lockwire-0.2.3/src/lockwire/examples/rover/starter.json +1 -0
- lockwire-0.2.3/src/lockwire/examples/servo-bench.yaml +113 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/gitref.py +1 -3
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/page.py +147 -42
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/plan.py +33 -8
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/plan_payload.py +3 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/report.py +58 -4
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/report_html.py +17 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/review_draft.py +1 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/scan.py +57 -22
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/scene_import.py +2 -6
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/scene_urdf.py +12 -10
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/scene_view.py +21 -6
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/schema.py +87 -42
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/serve.py +70 -11
- lockwire-0.2.3/src/lockwire/start_operation.py +170 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/urdf.py +8 -0
- lockwire-0.2.0/src/lockwire/advice.py +0 -119
- lockwire-0.2.0/src/lockwire/start_operation.py +0 -92
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/__main__.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/api.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/blame.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/catalog.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/cli_line.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/constraints.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/export_mhs.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/factdiff.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/forces.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/hw.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/mcp_server.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/mechanism.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/mesh_dae.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/mesh_simple.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/plugins.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/report_json.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/scene.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/sdf.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/statics.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/units.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/vcs.py +0 -0
- {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/whatif.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lockwire
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
Summary: The physical-assumption contract for robots:
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: The physical-assumption contract for robots: checks that a robot's declared parts (power, weight, bus, torque) add up
|
|
5
5
|
Keywords: robotics,urdf,ros,preflight,lockfile,ci,hardware,drone
|
|
6
6
|
Author: Shivansh
|
|
7
7
|
License-Expression: Apache-2.0
|
|
@@ -30,45 +30,156 @@ Provides-Extra: mcp
|
|
|
30
30
|
Provides-Extra: serve
|
|
31
31
|
Description-Content-Type: text/markdown
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
<p align="center">
|
|
34
|
+
<picture>
|
|
35
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/img/logo-dark.svg">
|
|
36
|
+
<img src="docs/img/logo-light.svg" alt="lockwire" height="64">
|
|
37
|
+
</picture>
|
|
38
|
+
</p>
|
|
34
39
|
|
|
35
40
|
**Find out your robot won't work before you buy the parts.**
|
|
36
41
|
|
|
42
|
+

|
|
43
|
+
|
|
37
44
|
Every robot is a stack of numbers from a dozen datasheets: what each part weighs, what each power rail can supply, how much the bus can carry, how much torque each joint gets. Lockwire keeps those numbers in one file and tells you the moment they stop adding up. Use it while you're still planning a build, then keep it in CI so a part swap can't quietly break it later.
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
## Try it in 30 seconds
|
|
47
|
+
|
|
48
|
+
Needs Python 3.12 or newer (the Python that ships with macOS is older).
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pipx install lockwire
|
|
52
|
+
lockwire start --example servo-bench
|
|
53
|
+
lockwire check
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```console
|
|
57
|
+
VIOLATION budgets/bud.power.servo-5v — observed 32.10 W, expected <= 15.00 W (Δ +17.10 W (+114%))
|
|
58
|
+
dependent: dev.pi5, dev.servo1, dev.servo2, dev.servo3, dev.servo4
|
|
59
|
+
...
|
|
60
|
+
NO-GO — 1 violation(s), 0 warning(s)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
A Raspberry Pi 5 and four servos on one 3 A regulator: caught before you ordered either.
|
|
64
|
+
|
|
65
|
+
## Try it on your robot
|
|
40
66
|
|
|
41
|
-
|
|
67
|
+
Point it at your URDF and see what it flags:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
lockwire start path/to/robot.urdf
|
|
71
|
+
lockwire check
|
|
72
|
+
```
|
|
42
73
|
|
|
43
|
-
|
|
74
|
+
No URDF? Start from the example and swap in your parts:
|
|
44
75
|
|
|
45
76
|
```bash
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
lockwire
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
lockwire add
|
|
52
|
-
|
|
77
|
+
lockwire start --example servo-bench
|
|
78
|
+
lockwire add device lockwire.yaml --id dev.servo5 --family fam.servo --model mod.mg996r --rail rail.servo-5v --draw "6 W" --mass "55 g" --owner you --source "MG996R datasheet" --witness Sam
|
|
79
|
+
lockwire check
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
See `lockwire add --help` for rails, budgets and the rest.
|
|
83
|
+
|
|
84
|
+
## Download the app
|
|
85
|
+
|
|
86
|
+
The free desktop app does the same visually. Get it from the [latest release](https://github.com/Shivansh2703/lockwire/releases/latest).
|
|
87
|
+
|
|
88
|
+

|
|
89
|
+
|
|
90
|
+
- **macOS (Apple silicon):** unzip, open Lockwire. The first open shows a warning because the app is not notarized: open System Settings, Privacy & Security, scroll down and click "Open Anyway".
|
|
91
|
+
- **Linux:** `chmod +x Lockwire-*.AppImage`, then run it. Ubuntu 22.04 needs `sudo apt install libfuse2`; Ubuntu 24.04 needs `sudo apt install libfuse2t64`.
|
|
92
|
+
- **Windows app:** not yet.
|
|
93
|
+
|
|
94
|
+
## Other ways to use it
|
|
95
|
+
|
|
96
|
+
**Command line.** Make a lockfile and check it from a terminal.
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
pipx install lockwire
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
lockwire start --example drone
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Starters: `lockwire start --example <name>` with drone, rover, arm, quadruped or humanoid. Add your own: see [CONTRIBUTING](CONTRIBUTING.md).
|
|
107
|
+
|
|
108
|
+
```
|
|
53
109
|
lockwire check lockwire.yaml
|
|
54
110
|
```
|
|
55
111
|
|
|
56
|
-
|
|
112
|
+
**Desktop app.** Download it above, click a New robot starter, or open your own lockfile.
|
|
57
113
|
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
114
|
+
```
|
|
115
|
+
https://github.com/Shivansh2703/lockwire/releases/latest
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**GitHub Action.** Run the check on every push and keep an HTML report.
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
- uses: actions/checkout@v4
|
|
122
|
+
- uses: Shivansh2703/lockwire@v0
|
|
123
|
+
with:
|
|
124
|
+
contract: lockwire.yaml
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**AI agents (MCP).** Let Claude Desktop, Claude Code or Cursor check your robot before it acts.
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
pipx install "lockwire[mcp]"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
claude mcp add lockwire -- lockwire mcp
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
{
|
|
139
|
+
"mcpServers": {
|
|
140
|
+
"lockwire": { "command": "lockwire", "args": ["mcp"] }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Claude Code: the claude mcp add line. Claude Desktop: put the JSON in claude_desktop_config.json. Cursor: put the JSON in .cursor/mcp.json (or ~/.cursor/mcp.json). If the app cannot find lockwire, use the full path from which lockwire. Then ask: Check my robot's lockwire.yaml and explain the NO-GO.
|
|
146
|
+
|
|
147
|
+
**Agent plan and what-if.** Score an intended plan, or a proposed change, before anything executes.
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
lockwire plan lockwire.yaml plan.yaml
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
lockwire what-if lockwire.yaml --change "devices/dev.onboard-computer/physical/mass=300 g" --source "my scale" --witness me
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
plan.yaml holds steps, for example: steps: [{kind: rail_load, rail: rail.supply-5v, draw: {value: 10, unit: W}}]
|
|
158
|
+
|
|
159
|
+
**Python library.** Call the same check from your own script.
|
|
67
160
|
|
|
68
|
-
NO-GO — 1 violation(s), 1 warning(s)
|
|
69
161
|
```
|
|
162
|
+
import lockwire.api as lw
|
|
163
|
+
print(lw.check("lockwire.yaml")["verdict"])
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**ROS, preflight and the local API.** The go/no-go gate for a powered-on robot, ROS and the local server.
|
|
167
|
+
|
|
168
|
+
Part of Lockwire Pro.
|
|
169
|
+
|
|
170
|
+
### Features
|
|
70
171
|
|
|
71
|
-
|
|
172
|
+
- **Check:** See which numbers do not add up. `lockwire check lockwire.yaml`
|
|
173
|
+
- **Declare a fact:** Set one value with a source and a witness. `lockwire declare lockwire.yaml devices/dev.power-module.pm02-v3/physical/mass "25 g" --source "my scale" --witness me`
|
|
174
|
+
- **Add a part:** Add a device, rail, bus or battery block. `lockwire add --help`
|
|
175
|
+
- **Design (place, move, rotate, reparent):** Put a part in the 3D model and move it. Switch on the Power, Data and Mechanical connection views to see them drawn on the robot, and use W, A, S, D to move the camera. `lockwire move lockwire.yaml dev.power-module.pm02-v3 --offset 0.01,0,0.02 --unit m --source "tape measure" --witness me`
|
|
176
|
+
- **Plan and what-if:** Score a plan or a change without editing the lockfile. `lockwire plan lockwire.yaml plan.yaml`
|
|
177
|
+
- **Report:** Write one HTML page to share. `lockwire report lockwire.yaml -o report.html --no-open`
|
|
178
|
+
- **Describe:** Write a reference sheet per part. `lockwire describe lockwire.yaml --out ref`
|
|
179
|
+
- **History (diff, log, blame):** See what changed, by whom, and why (needs git). `lockwire diff`
|
|
180
|
+
- **Scene:** Read part positions from a 3D model. `lockwire scene import --help`
|
|
181
|
+
- **Gearbox:** Add a gearbox with its ratio and joints. `lockwire gearbox --help`
|
|
182
|
+
- **Upgrade:** Write a new lockfile from an older one. `lockwire upgrade --help`
|
|
72
183
|
|
|
73
184
|
## A real-world example: the X500 drone
|
|
74
185
|
|
|
@@ -141,8 +252,8 @@ The `scored N: …` line above the verdict is where a script should look.
|
|
|
141
252
|
contract: lockwire.yaml
|
|
142
253
|
```
|
|
143
254
|
|
|
144
|
-
Inputs: `contract` (default `lockwire.yaml`), `version` (default
|
|
145
|
-
Or just `
|
|
255
|
+
Inputs: `contract` (default `lockwire.yaml`), `version` (pin a PyPI release; default installs the action's own code), `python-version` (default 3.12).
|
|
256
|
+
Or just `pipx install lockwire && lockwire check lockwire.yaml`.
|
|
146
257
|
|
|
147
258
|
## Pre-commit
|
|
148
259
|
|
|
@@ -179,8 +290,7 @@ No physics simulation, no machine learning, no network. The answer is the same t
|
|
|
179
290
|
- **Free forever (this repo):** the lockfile, all deterministic checks including
|
|
180
291
|
power/thermal/bus/link/torque budgets, edit / describe / plan / what-if, the MCP server, the
|
|
181
292
|
HTML report, and the desktop planning app (Open, Design, Declare, Plan, Check).
|
|
182
|
-
- **Lockwire Pro**
|
|
183
|
-
(rosbag2 / ULog), and health.
|
|
293
|
+
- **Lockwire Pro** is the paid tier (powered-on preflight, recording analysis).
|
|
184
294
|
|
|
185
295
|
## Development
|
|
186
296
|
|
|
@@ -1,42 +1,153 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/img/logo-dark.svg">
|
|
4
|
+
<img src="docs/img/logo-light.svg" alt="lockwire" height="64">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
2
7
|
|
|
3
8
|
**Find out your robot won't work before you buy the parts.**
|
|
4
9
|
|
|
10
|
+

|
|
11
|
+
|
|
5
12
|
Every robot is a stack of numbers from a dozen datasheets: what each part weighs, what each power rail can supply, how much the bus can carry, how much torque each joint gets. Lockwire keeps those numbers in one file and tells you the moment they stop adding up. Use it while you're still planning a build, then keep it in CI so a part swap can't quietly break it later.
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
## Try it in 30 seconds
|
|
15
|
+
|
|
16
|
+
Needs Python 3.12 or newer (the Python that ships with macOS is older).
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pipx install lockwire
|
|
20
|
+
lockwire start --example servo-bench
|
|
21
|
+
lockwire check
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```console
|
|
25
|
+
VIOLATION budgets/bud.power.servo-5v — observed 32.10 W, expected <= 15.00 W (Δ +17.10 W (+114%))
|
|
26
|
+
dependent: dev.pi5, dev.servo1, dev.servo2, dev.servo3, dev.servo4
|
|
27
|
+
...
|
|
28
|
+
NO-GO — 1 violation(s), 0 warning(s)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
A Raspberry Pi 5 and four servos on one 3 A regulator: caught before you ordered either.
|
|
32
|
+
|
|
33
|
+
## Try it on your robot
|
|
8
34
|
|
|
9
|
-
|
|
35
|
+
Point it at your URDF and see what it flags:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
lockwire start path/to/robot.urdf
|
|
39
|
+
lockwire check
|
|
40
|
+
```
|
|
10
41
|
|
|
11
|
-
|
|
42
|
+
No URDF? Start from the example and swap in your parts:
|
|
12
43
|
|
|
13
44
|
```bash
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
lockwire
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
lockwire add
|
|
20
|
-
|
|
45
|
+
lockwire start --example servo-bench
|
|
46
|
+
lockwire add device lockwire.yaml --id dev.servo5 --family fam.servo --model mod.mg996r --rail rail.servo-5v --draw "6 W" --mass "55 g" --owner you --source "MG996R datasheet" --witness Sam
|
|
47
|
+
lockwire check
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
See `lockwire add --help` for rails, budgets and the rest.
|
|
51
|
+
|
|
52
|
+
## Download the app
|
|
53
|
+
|
|
54
|
+
The free desktop app does the same visually. Get it from the [latest release](https://github.com/Shivansh2703/lockwire/releases/latest).
|
|
55
|
+
|
|
56
|
+

|
|
57
|
+
|
|
58
|
+
- **macOS (Apple silicon):** unzip, open Lockwire. The first open shows a warning because the app is not notarized: open System Settings, Privacy & Security, scroll down and click "Open Anyway".
|
|
59
|
+
- **Linux:** `chmod +x Lockwire-*.AppImage`, then run it. Ubuntu 22.04 needs `sudo apt install libfuse2`; Ubuntu 24.04 needs `sudo apt install libfuse2t64`.
|
|
60
|
+
- **Windows app:** not yet.
|
|
61
|
+
|
|
62
|
+
## Other ways to use it
|
|
63
|
+
|
|
64
|
+
**Command line.** Make a lockfile and check it from a terminal.
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
pipx install lockwire
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
lockwire start --example drone
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Starters: `lockwire start --example <name>` with drone, rover, arm, quadruped or humanoid. Add your own: see [CONTRIBUTING](CONTRIBUTING.md).
|
|
75
|
+
|
|
76
|
+
```
|
|
21
77
|
lockwire check lockwire.yaml
|
|
22
78
|
```
|
|
23
79
|
|
|
24
|
-
|
|
80
|
+
**Desktop app.** Download it above, click a New robot starter, or open your own lockfile.
|
|
25
81
|
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
82
|
+
```
|
|
83
|
+
https://github.com/Shivansh2703/lockwire/releases/latest
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**GitHub Action.** Run the check on every push and keep an HTML report.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
- uses: actions/checkout@v4
|
|
90
|
+
- uses: Shivansh2703/lockwire@v0
|
|
91
|
+
with:
|
|
92
|
+
contract: lockwire.yaml
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**AI agents (MCP).** Let Claude Desktop, Claude Code or Cursor check your robot before it acts.
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
pipx install "lockwire[mcp]"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
claude mcp add lockwire -- lockwire mcp
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
{
|
|
107
|
+
"mcpServers": {
|
|
108
|
+
"lockwire": { "command": "lockwire", "args": ["mcp"] }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Claude Code: the claude mcp add line. Claude Desktop: put the JSON in claude_desktop_config.json. Cursor: put the JSON in .cursor/mcp.json (or ~/.cursor/mcp.json). If the app cannot find lockwire, use the full path from which lockwire. Then ask: Check my robot's lockwire.yaml and explain the NO-GO.
|
|
114
|
+
|
|
115
|
+
**Agent plan and what-if.** Score an intended plan, or a proposed change, before anything executes.
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
lockwire plan lockwire.yaml plan.yaml
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
lockwire what-if lockwire.yaml --change "devices/dev.onboard-computer/physical/mass=300 g" --source "my scale" --witness me
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
plan.yaml holds steps, for example: steps: [{kind: rail_load, rail: rail.supply-5v, draw: {value: 10, unit: W}}]
|
|
126
|
+
|
|
127
|
+
**Python library.** Call the same check from your own script.
|
|
35
128
|
|
|
36
|
-
NO-GO — 1 violation(s), 1 warning(s)
|
|
37
129
|
```
|
|
130
|
+
import lockwire.api as lw
|
|
131
|
+
print(lw.check("lockwire.yaml")["verdict"])
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**ROS, preflight and the local API.** The go/no-go gate for a powered-on robot, ROS and the local server.
|
|
135
|
+
|
|
136
|
+
Part of Lockwire Pro.
|
|
137
|
+
|
|
138
|
+
### Features
|
|
38
139
|
|
|
39
|
-
|
|
140
|
+
- **Check:** See which numbers do not add up. `lockwire check lockwire.yaml`
|
|
141
|
+
- **Declare a fact:** Set one value with a source and a witness. `lockwire declare lockwire.yaml devices/dev.power-module.pm02-v3/physical/mass "25 g" --source "my scale" --witness me`
|
|
142
|
+
- **Add a part:** Add a device, rail, bus or battery block. `lockwire add --help`
|
|
143
|
+
- **Design (place, move, rotate, reparent):** Put a part in the 3D model and move it. Switch on the Power, Data and Mechanical connection views to see them drawn on the robot, and use W, A, S, D to move the camera. `lockwire move lockwire.yaml dev.power-module.pm02-v3 --offset 0.01,0,0.02 --unit m --source "tape measure" --witness me`
|
|
144
|
+
- **Plan and what-if:** Score a plan or a change without editing the lockfile. `lockwire plan lockwire.yaml plan.yaml`
|
|
145
|
+
- **Report:** Write one HTML page to share. `lockwire report lockwire.yaml -o report.html --no-open`
|
|
146
|
+
- **Describe:** Write a reference sheet per part. `lockwire describe lockwire.yaml --out ref`
|
|
147
|
+
- **History (diff, log, blame):** See what changed, by whom, and why (needs git). `lockwire diff`
|
|
148
|
+
- **Scene:** Read part positions from a 3D model. `lockwire scene import --help`
|
|
149
|
+
- **Gearbox:** Add a gearbox with its ratio and joints. `lockwire gearbox --help`
|
|
150
|
+
- **Upgrade:** Write a new lockfile from an older one. `lockwire upgrade --help`
|
|
40
151
|
|
|
41
152
|
## A real-world example: the X500 drone
|
|
42
153
|
|
|
@@ -109,8 +220,8 @@ The `scored N: …` line above the verdict is where a script should look.
|
|
|
109
220
|
contract: lockwire.yaml
|
|
110
221
|
```
|
|
111
222
|
|
|
112
|
-
Inputs: `contract` (default `lockwire.yaml`), `version` (default
|
|
113
|
-
Or just `
|
|
223
|
+
Inputs: `contract` (default `lockwire.yaml`), `version` (pin a PyPI release; default installs the action's own code), `python-version` (default 3.12).
|
|
224
|
+
Or just `pipx install lockwire && lockwire check lockwire.yaml`.
|
|
114
225
|
|
|
115
226
|
## Pre-commit
|
|
116
227
|
|
|
@@ -147,8 +258,7 @@ No physics simulation, no machine learning, no network. The answer is the same t
|
|
|
147
258
|
- **Free forever (this repo):** the lockfile, all deterministic checks including
|
|
148
259
|
power/thermal/bus/link/torque budgets, edit / describe / plan / what-if, the MCP server, the
|
|
149
260
|
HTML report, and the desktop planning app (Open, Design, Declare, Plan, Check).
|
|
150
|
-
- **Lockwire Pro**
|
|
151
|
-
(rosbag2 / ULog), and health.
|
|
261
|
+
- **Lockwire Pro** is the paid tier (powered-on preflight, recording analysis).
|
|
152
262
|
|
|
153
263
|
## Development
|
|
154
264
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "lockwire"
|
|
3
|
-
version = "0.2.
|
|
4
|
-
description = "The physical-assumption contract for robots:
|
|
3
|
+
version = "0.2.3"
|
|
4
|
+
description = "The physical-assumption contract for robots: checks that a robot's declared parts (power, weight, bus, torque) add up"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
7
7
|
{ name = "Shivansh" }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Blocking exclusive lock on an open fd, POSIX and Windows, stdlib only."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def lock(fd: int) -> None:
|
|
10
|
+
if os.name == "nt":
|
|
11
|
+
import msvcrt
|
|
12
|
+
|
|
13
|
+
os.lseek(fd, 0, os.SEEK_SET)
|
|
14
|
+
deadline = time.monotonic() + 30
|
|
15
|
+
while True:
|
|
16
|
+
try:
|
|
17
|
+
msvcrt.locking(fd, msvcrt.LK_NBLCK, 1)
|
|
18
|
+
return
|
|
19
|
+
except OSError:
|
|
20
|
+
if time.monotonic() >= deadline:
|
|
21
|
+
raise TimeoutError("lock held by another lockwire process") from None
|
|
22
|
+
time.sleep(0.1)
|
|
23
|
+
else:
|
|
24
|
+
import fcntl
|
|
25
|
+
|
|
26
|
+
fcntl.flock(fd, fcntl.LOCK_EX)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def unlock(fd: int) -> None:
|
|
30
|
+
if os.name == "nt":
|
|
31
|
+
import msvcrt
|
|
32
|
+
|
|
33
|
+
os.lseek(fd, 0, os.SEEK_SET)
|
|
34
|
+
msvcrt.locking(fd, msvcrt.LK_UNLCK, 1)
|
|
35
|
+
else:
|
|
36
|
+
import fcntl
|
|
37
|
+
|
|
38
|
+
fcntl.flock(fd, fcntl.LOCK_UN)
|