jolly-cli 0.3.0__tar.gz → 0.5.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.
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/PKG-INFO +88 -24
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/README.md +82 -21
- jolly_cli-0.5.0/docs/assets/jolly-so101-viewer.png +0 -0
- jolly_cli-0.5.0/docs/assets/jolly-terminal-pick-place-v050.gif +0 -0
- jolly_cli-0.5.0/docs/assets/jolly-terminal-pick-place-v050.mp4 +0 -0
- jolly_cli-0.5.0/docs/assets/jolly-web-demo.gif +0 -0
- jolly_cli-0.5.0/docs/assets/jolly-web-demo.webm +0 -0
- jolly_cli-0.5.0/docs/hardware/so101-calibration.example.json +10 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/docs/wiki/CLI-Reference.md +20 -2
- jolly_cli-0.5.0/docs/wiki/Challenges-and-Benchmarks.md +36 -0
- jolly_cli-0.5.0/docs/wiki/Physical-SO-101-Hardware.md +35 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/docs/wiki/Web-Viewer.md +9 -1
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/docs/wiki/_Sidebar.md +1 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/__init__.py +1 -1
- jolly_cli-0.5.0/jolly/benchmark.py +85 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/challenges.py +23 -1
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/cli.py +218 -11
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/core/physics.py +84 -33
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/core/scenes.py +11 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/driver.py +14 -0
- jolly_cli-0.5.0/jolly/hardware/__init__.py +3 -0
- jolly_cli-0.5.0/jolly/hardware/so101.py +297 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/web/app.py +36 -1
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/pyproject.toml +5 -4
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/skills/jolly/SKILL.md +8 -4
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/tests/test_cli.py +63 -4
- jolly_cli-0.5.0/tests/test_hardware.py +111 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/tests/test_physics.py +14 -2
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/tests/test_randomization.py +6 -14
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/tests/test_web.py +42 -0
- jolly_cli-0.3.0/docs/assets/jolly-so101-viewer.png +0 -0
- jolly_cli-0.3.0/docs/assets/jolly-terminal-demo.gif +0 -0
- jolly_cli-0.3.0/docs/assets/jolly-terminal-demo.mp4 +0 -0
- jolly_cli-0.3.0/docs/assets/jolly-web-demo.gif +0 -0
- jolly_cli-0.3.0/docs/assets/jolly-web-demo.webm +0 -0
- jolly_cli-0.3.0/docs/wiki/Challenges-and-Benchmarks.md +0 -29
- jolly_cli-0.3.0/jolly/benchmark.py +0 -101
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/.github/workflows/publish-pypi.yml +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/.gitignore +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/LICENSE-APACHE +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/LICENSE-MIT +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/SKILL.md +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/THIRD_PARTY_LICENSES.md +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/docs/wiki/Home.md +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/docs/wiki/Installation.md +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/docs/wiki/LLM-Agent-Safety.md +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/docs/wiki/Robot-Models-and-Licensing.md +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/__main__.py +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/__init__.py +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/jolly6.urdf +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/CITATION.cff +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/LICENSE-APACHE +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/SOURCE.md +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/UPSTREAM-README.md +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/assets/base_motor_holder_so101_v1.stl +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/assets/base_so101_v2.stl +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/assets/motor_holder_so101_base_v1.stl +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/assets/motor_holder_so101_wrist_v1.stl +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/assets/moving_jaw_so101_v1.stl +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/assets/rotation_pitch_so101_v1.stl +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/assets/sts3215_03a_no_horn_v1.stl +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/assets/sts3215_03a_v1.stl +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/assets/under_arm_so101_v1.stl +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/assets/upper_arm_so101_v1.stl +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/assets/waveshare_mounting_plate_so101_v2.stl +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/assets/wrist_roll_follower_so101_v1.stl +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/assets/wrist_roll_pitch_so101_v2.stl +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/assets/robots/so101/so101_new_calib.urdf +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/core/__init__.py +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/core/errors.py +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/core/models.py +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/core/store.py +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/engine.py +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/web/__init__.py +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/jolly/web/index.html +0 -0
- {jolly_cli-0.3.0 → jolly_cli-0.5.0}/tests/test_models.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jolly-cli
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: CLI-native SO-101 physics tasks and physical hardware control
|
|
5
5
|
Project-URL: Homepage, https://github.com/TeamAudiyo/Jolly
|
|
6
6
|
Project-URL: Repository, https://github.com/TeamAudiyo/Jolly
|
|
7
7
|
Project-URL: Issues, https://github.com/TeamAudiyo/Jolly/issues
|
|
@@ -9,7 +9,7 @@ Author: TeamAudiyo
|
|
|
9
9
|
License: MIT OR Apache-2.0
|
|
10
10
|
License-File: LICENSE-APACHE
|
|
11
11
|
License-File: LICENSE-MIT
|
|
12
|
-
Keywords: cli,kinematics,llm,pybullet,robotics,
|
|
12
|
+
Keywords: cli,hardware,kinematics,llm,pybullet,robotics,so101
|
|
13
13
|
Classifier: Development Status :: 3 - Alpha
|
|
14
14
|
Classifier: Environment :: Console
|
|
15
15
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
@@ -27,8 +27,11 @@ Requires-Dist: rich<15,>=13.7
|
|
|
27
27
|
Provides-Extra: dev
|
|
28
28
|
Requires-Dist: build<2,>=1.2; extra == 'dev'
|
|
29
29
|
Requires-Dist: httpx<1,>=0.27; extra == 'dev'
|
|
30
|
+
Requires-Dist: pyserial<4,>=3.5; extra == 'dev'
|
|
30
31
|
Requires-Dist: pytest-cov<7,>=5; extra == 'dev'
|
|
31
32
|
Requires-Dist: pytest<9,>=8.2; extra == 'dev'
|
|
33
|
+
Provides-Extra: hardware
|
|
34
|
+
Requires-Dist: pyserial<4,>=3.5; extra == 'hardware'
|
|
32
35
|
Provides-Extra: web
|
|
33
36
|
Requires-Dist: fastapi<1,>=0.115; extra == 'web'
|
|
34
37
|
Requires-Dist: uvicorn<1,>=0.30; extra == 'web'
|
|
@@ -36,8 +39,15 @@ Description-Content-Type: text/markdown
|
|
|
36
39
|
|
|
37
40
|
# Jolly CLI
|
|
38
41
|
|
|
39
|
-
Jolly
|
|
40
|
-
|
|
42
|
+
Jolly provides two separate robot-control backends for terminal users and LLM
|
|
43
|
+
agents:
|
|
44
|
+
|
|
45
|
+
- `JollyEngine` executes explicit operator controls with the official SO-101
|
|
46
|
+
model in PyBullet, then measures the final physical state.
|
|
47
|
+
- `SO101HardwareDriver` sends the real STS3215 serial protocol to a physical
|
|
48
|
+
SO-101 and reads measured motor positions.
|
|
49
|
+
|
|
50
|
+
The two backends never combine their results.
|
|
41
51
|
|
|
42
52
|
Jolly includes two offline robot profiles:
|
|
43
53
|
|
|
@@ -48,20 +58,30 @@ The package also accepts scenes, challenges, an engine benchmark, a native
|
|
|
48
58
|
PyBullet viewer, and an optional local web viewer. No cloud service, MCP server,
|
|
49
59
|
API key, or network connection is required at runtime.
|
|
50
60
|
|
|
51
|
-
## See Jolly in action
|
|
61
|
+
## See Jolly 0.5.0 in action
|
|
62
|
+
|
|
63
|
+
### Interactive SO-101 simulator
|
|
64
|
+
|
|
65
|
+

|
|
52
66
|
|
|
53
|
-
|
|
67
|
+
[Watch the full WebM recording](docs/assets/jolly-web-demo.webm) ·
|
|
68
|
+
[Open the full-resolution still](docs/assets/jolly-so101-viewer.png)
|
|
54
69
|
|
|
55
|
-
|
|
70
|
+
The recording uses the official SO-101 CAD meshes in a real PyBullet world. It
|
|
71
|
+
executes a Cartesian reach in the insertion scene. The viewport is not a
|
|
72
|
+
diagram or a hand-drawn robot substitute.
|
|
56
73
|
|
|
57
|
-
|
|
58
|
-
It is not a diagram or a hand-drawn robot substitute.
|
|
74
|
+
### Operator-controlled randomized pick-and-place
|
|
59
75
|
|
|
60
|
-
|
|
76
|
+

|
|
61
77
|
|
|
62
|
-
|
|
78
|
+
[Watch the MP4 recording](docs/assets/jolly-terminal-pick-place-v050.mp4)
|
|
63
79
|
|
|
64
|
-
|
|
80
|
+
The terminal recording shows every control: move above the peg, move down,
|
|
81
|
+
close the gripper, lift, move sideways, lower, and release. `benchmark score`
|
|
82
|
+
does not move the robot and does not generate a numeric score. It reports PASS
|
|
83
|
+
only from the final measured peg pose, release state, collision state, and
|
|
84
|
+
command budget.
|
|
65
85
|
|
|
66
86
|
## Install
|
|
67
87
|
|
|
@@ -99,7 +119,9 @@ jolly move --joints "0,-20,40,-20,0" --gripper 0.0 --json
|
|
|
99
119
|
jolly reach --x 0.30 --y 0.10 --z 0.18 --gripper 0.0 --json
|
|
100
120
|
jolly render
|
|
101
121
|
jolly challenge list --json
|
|
102
|
-
jolly benchmark --json
|
|
122
|
+
jolly benchmark start --seed 42017 --model so101 --json
|
|
123
|
+
# Issue explicit jolly reach controls using the generated peg and hole coordinates.
|
|
124
|
+
jolly benchmark score --json
|
|
103
125
|
```
|
|
104
126
|
|
|
105
127
|
Start the optional local web viewer:
|
|
@@ -140,7 +162,8 @@ Important commands:
|
|
|
140
162
|
| `jolly reset` | Select a model and scene, then home the robot. |
|
|
141
163
|
| `jolly scene list/load` | Discover and load deterministic scenes. |
|
|
142
164
|
| `jolly challenge list/start/status` | Run seeded randomized manipulation challenges. |
|
|
143
|
-
| `jolly benchmark
|
|
165
|
+
| `jolly benchmark start/score` | Generate a randomized pick-and-place task, accept explicit controls, then measure PASS or FAIL. |
|
|
166
|
+
| `jolly hardware state/move/benchmark/stop` | Control and measure a physical SO-101 over its serial bus. |
|
|
144
167
|
|
|
145
168
|
Jolly rejects joint-limit violations and unsafe workspace targets. By default,
|
|
146
169
|
Jolly rolls back a motion that creates a collision. Use `--allow-collision`
|
|
@@ -184,15 +207,15 @@ jolly-cli/
|
|
|
184
207
|
|
|
185
208
|
## Physics scope
|
|
186
209
|
|
|
187
|
-
`JollyEngine` owns state, safety, motion rollback, grasping,
|
|
188
|
-
|
|
189
|
-
benchmark cases. The project does not depend on Inspect, Inspect
|
|
190
|
-
external robotics benchmark driver.
|
|
210
|
+
`JollyEngine` owns state, safety, motion rollback, constrained grasping,
|
|
211
|
+
rendering, and the physics-task contract. `JollyDriver` generates seeded
|
|
212
|
+
challenge and benchmark cases. The project does not depend on Inspect, Inspect
|
|
213
|
+
AI, or an external robotics benchmark driver.
|
|
191
214
|
|
|
192
215
|
PyBullet is the low-level open-source physics backend. It performs rigid-body
|
|
193
216
|
simulation, FK, IK, contact generation, and scene settling. Jolly uses
|
|
194
217
|
deterministic state restoration across short CLI processes.
|
|
195
|
-
The grasp helper attaches
|
|
218
|
+
The grasp helper attaches one contacted graspable object while the gripper is closed.
|
|
196
219
|
This helper makes terminal pick-and-place repeatable. It is not a soft-contact or
|
|
197
220
|
motor-current model.
|
|
198
221
|
|
|
@@ -204,12 +227,53 @@ The result includes the seed and full instance so results stay auditable. Omit
|
|
|
204
227
|
|
|
205
228
|
```bash
|
|
206
229
|
jolly challenge start sort-red --seed 42017 --json
|
|
207
|
-
jolly benchmark --seed 42017 --
|
|
230
|
+
jolly benchmark start --seed 42017 --model so101 --json
|
|
231
|
+
# Read the generated instance, then issue each reach control yourself.
|
|
232
|
+
jolly benchmark score --json
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Benchmark start generates the randomized peg and hole but executes no motion.
|
|
236
|
+
Only explicit `jolly reach` or `jolly move` commands can change the attempt.
|
|
237
|
+
Benchmark score executes no motion and returns no numeric score. PASS requires
|
|
238
|
+
the released peg to settle inside the generated hole without collision and
|
|
239
|
+
within the command budget. The JSON result includes every requested control and
|
|
240
|
+
its measured tool, gripper, grasp, and collision state.
|
|
241
|
+
|
|
242
|
+
## Physical SO-101 hardware
|
|
243
|
+
|
|
244
|
+
Install direct physical-hardware support:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
python -m pip install 'jolly-cli[hardware]'
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Jolly implements the Feetech STS3215 packet protocol directly. It does not use
|
|
251
|
+
Inspect Robots or an external robot SDK. Copy and calibrate the example file
|
|
252
|
+
before enabling torque:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
jolly hardware calibration-example --output so101-calibration.json
|
|
256
|
+
# Replace every placeholder with measurements from this physical arm.
|
|
257
|
+
jolly hardware scan --port /dev/ttyACM0 --calibration so101-calibration.json --json
|
|
258
|
+
jolly hardware state --port /dev/ttyACM0 --calibration so101-calibration.json --json
|
|
259
|
+
jolly hardware move --port /dev/ttyACM0 --calibration so101-calibration.json \
|
|
260
|
+
--joints '0,0,0,0,0' --gripper 0 --confirm-hardware --json
|
|
261
|
+
jolly hardware benchmark --port /dev/ttyACM0 --calibration so101-calibration.json \
|
|
262
|
+
--seed 42017 --cases 3 --confirm-hardware --json
|
|
263
|
+
jolly hardware stop --port /dev/ttyACM0 --calibration so101-calibration.json
|
|
208
264
|
```
|
|
209
265
|
|
|
210
|
-
The
|
|
211
|
-
|
|
212
|
-
|
|
266
|
+
The example calibration values are placeholders and set `calibrated` to false.
|
|
267
|
+
Measure every zero point, direction, and gripper endpoint before setting it to
|
|
268
|
+
true. Physical commands require an explicit confirmation, enforce degree and
|
|
269
|
+
raw-count step limits, set bounded goal speed, issue simultaneous goals, and
|
|
270
|
+
read motor feedback. The driver disables torque after communication failures.
|
|
271
|
+
After successful movement it keeps torque enabled so the arm does not fall.
|
|
272
|
+
Support the arm before running `jolly hardware stop`.
|
|
273
|
+
|
|
274
|
+
The physical benchmark scores measured joint-position error only. The PyBullet
|
|
275
|
+
operator benchmark returns a measured PASS or FAIL without a numeric score.
|
|
276
|
+
Jolly never presents physics output as a physical-hardware result.
|
|
213
277
|
|
|
214
278
|
The bundled SO-101 model is the official Apache-2.0 new-calibration URDF from
|
|
215
279
|
The Robot Studio. Jolly includes the 13 referenced STL meshes from pinned commit
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# Jolly CLI
|
|
2
2
|
|
|
3
|
-
Jolly
|
|
4
|
-
|
|
3
|
+
Jolly provides two separate robot-control backends for terminal users and LLM
|
|
4
|
+
agents:
|
|
5
|
+
|
|
6
|
+
- `JollyEngine` executes explicit operator controls with the official SO-101
|
|
7
|
+
model in PyBullet, then measures the final physical state.
|
|
8
|
+
- `SO101HardwareDriver` sends the real STS3215 serial protocol to a physical
|
|
9
|
+
SO-101 and reads measured motor positions.
|
|
10
|
+
|
|
11
|
+
The two backends never combine their results.
|
|
5
12
|
|
|
6
13
|
Jolly includes two offline robot profiles:
|
|
7
14
|
|
|
@@ -12,20 +19,30 @@ The package also accepts scenes, challenges, an engine benchmark, a native
|
|
|
12
19
|
PyBullet viewer, and an optional local web viewer. No cloud service, MCP server,
|
|
13
20
|
API key, or network connection is required at runtime.
|
|
14
21
|
|
|
15
|
-
## See Jolly in action
|
|
22
|
+
## See Jolly 0.5.0 in action
|
|
23
|
+
|
|
24
|
+
### Interactive SO-101 simulator
|
|
25
|
+
|
|
26
|
+

|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
[Watch the full WebM recording](docs/assets/jolly-web-demo.webm) ·
|
|
29
|
+
[Open the full-resolution still](docs/assets/jolly-so101-viewer.png)
|
|
18
30
|
|
|
19
|
-
|
|
31
|
+
The recording uses the official SO-101 CAD meshes in a real PyBullet world. It
|
|
32
|
+
executes a Cartesian reach in the insertion scene. The viewport is not a
|
|
33
|
+
diagram or a hand-drawn robot substitute.
|
|
20
34
|
|
|
21
|
-
|
|
22
|
-
It is not a diagram or a hand-drawn robot substitute.
|
|
35
|
+
### Operator-controlled randomized pick-and-place
|
|
23
36
|
|
|
24
|
-
|
|
37
|
+

|
|
25
38
|
|
|
26
|
-
|
|
39
|
+
[Watch the MP4 recording](docs/assets/jolly-terminal-pick-place-v050.mp4)
|
|
27
40
|
|
|
28
|
-
|
|
41
|
+
The terminal recording shows every control: move above the peg, move down,
|
|
42
|
+
close the gripper, lift, move sideways, lower, and release. `benchmark score`
|
|
43
|
+
does not move the robot and does not generate a numeric score. It reports PASS
|
|
44
|
+
only from the final measured peg pose, release state, collision state, and
|
|
45
|
+
command budget.
|
|
29
46
|
|
|
30
47
|
## Install
|
|
31
48
|
|
|
@@ -63,7 +80,9 @@ jolly move --joints "0,-20,40,-20,0" --gripper 0.0 --json
|
|
|
63
80
|
jolly reach --x 0.30 --y 0.10 --z 0.18 --gripper 0.0 --json
|
|
64
81
|
jolly render
|
|
65
82
|
jolly challenge list --json
|
|
66
|
-
jolly benchmark --json
|
|
83
|
+
jolly benchmark start --seed 42017 --model so101 --json
|
|
84
|
+
# Issue explicit jolly reach controls using the generated peg and hole coordinates.
|
|
85
|
+
jolly benchmark score --json
|
|
67
86
|
```
|
|
68
87
|
|
|
69
88
|
Start the optional local web viewer:
|
|
@@ -104,7 +123,8 @@ Important commands:
|
|
|
104
123
|
| `jolly reset` | Select a model and scene, then home the robot. |
|
|
105
124
|
| `jolly scene list/load` | Discover and load deterministic scenes. |
|
|
106
125
|
| `jolly challenge list/start/status` | Run seeded randomized manipulation challenges. |
|
|
107
|
-
| `jolly benchmark
|
|
126
|
+
| `jolly benchmark start/score` | Generate a randomized pick-and-place task, accept explicit controls, then measure PASS or FAIL. |
|
|
127
|
+
| `jolly hardware state/move/benchmark/stop` | Control and measure a physical SO-101 over its serial bus. |
|
|
108
128
|
|
|
109
129
|
Jolly rejects joint-limit violations and unsafe workspace targets. By default,
|
|
110
130
|
Jolly rolls back a motion that creates a collision. Use `--allow-collision`
|
|
@@ -148,15 +168,15 @@ jolly-cli/
|
|
|
148
168
|
|
|
149
169
|
## Physics scope
|
|
150
170
|
|
|
151
|
-
`JollyEngine` owns state, safety, motion rollback, grasping,
|
|
152
|
-
|
|
153
|
-
benchmark cases. The project does not depend on Inspect, Inspect
|
|
154
|
-
external robotics benchmark driver.
|
|
171
|
+
`JollyEngine` owns state, safety, motion rollback, constrained grasping,
|
|
172
|
+
rendering, and the physics-task contract. `JollyDriver` generates seeded
|
|
173
|
+
challenge and benchmark cases. The project does not depend on Inspect, Inspect
|
|
174
|
+
AI, or an external robotics benchmark driver.
|
|
155
175
|
|
|
156
176
|
PyBullet is the low-level open-source physics backend. It performs rigid-body
|
|
157
177
|
simulation, FK, IK, contact generation, and scene settling. Jolly uses
|
|
158
178
|
deterministic state restoration across short CLI processes.
|
|
159
|
-
The grasp helper attaches
|
|
179
|
+
The grasp helper attaches one contacted graspable object while the gripper is closed.
|
|
160
180
|
This helper makes terminal pick-and-place repeatable. It is not a soft-contact or
|
|
161
181
|
motor-current model.
|
|
162
182
|
|
|
@@ -168,12 +188,53 @@ The result includes the seed and full instance so results stay auditable. Omit
|
|
|
168
188
|
|
|
169
189
|
```bash
|
|
170
190
|
jolly challenge start sort-red --seed 42017 --json
|
|
171
|
-
jolly benchmark --seed 42017 --
|
|
191
|
+
jolly benchmark start --seed 42017 --model so101 --json
|
|
192
|
+
# Read the generated instance, then issue each reach control yourself.
|
|
193
|
+
jolly benchmark score --json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Benchmark start generates the randomized peg and hole but executes no motion.
|
|
197
|
+
Only explicit `jolly reach` or `jolly move` commands can change the attempt.
|
|
198
|
+
Benchmark score executes no motion and returns no numeric score. PASS requires
|
|
199
|
+
the released peg to settle inside the generated hole without collision and
|
|
200
|
+
within the command budget. The JSON result includes every requested control and
|
|
201
|
+
its measured tool, gripper, grasp, and collision state.
|
|
202
|
+
|
|
203
|
+
## Physical SO-101 hardware
|
|
204
|
+
|
|
205
|
+
Install direct physical-hardware support:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
python -m pip install 'jolly-cli[hardware]'
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Jolly implements the Feetech STS3215 packet protocol directly. It does not use
|
|
212
|
+
Inspect Robots or an external robot SDK. Copy and calibrate the example file
|
|
213
|
+
before enabling torque:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
jolly hardware calibration-example --output so101-calibration.json
|
|
217
|
+
# Replace every placeholder with measurements from this physical arm.
|
|
218
|
+
jolly hardware scan --port /dev/ttyACM0 --calibration so101-calibration.json --json
|
|
219
|
+
jolly hardware state --port /dev/ttyACM0 --calibration so101-calibration.json --json
|
|
220
|
+
jolly hardware move --port /dev/ttyACM0 --calibration so101-calibration.json \
|
|
221
|
+
--joints '0,0,0,0,0' --gripper 0 --confirm-hardware --json
|
|
222
|
+
jolly hardware benchmark --port /dev/ttyACM0 --calibration so101-calibration.json \
|
|
223
|
+
--seed 42017 --cases 3 --confirm-hardware --json
|
|
224
|
+
jolly hardware stop --port /dev/ttyACM0 --calibration so101-calibration.json
|
|
172
225
|
```
|
|
173
226
|
|
|
174
|
-
The
|
|
175
|
-
|
|
176
|
-
|
|
227
|
+
The example calibration values are placeholders and set `calibrated` to false.
|
|
228
|
+
Measure every zero point, direction, and gripper endpoint before setting it to
|
|
229
|
+
true. Physical commands require an explicit confirmation, enforce degree and
|
|
230
|
+
raw-count step limits, set bounded goal speed, issue simultaneous goals, and
|
|
231
|
+
read motor feedback. The driver disables torque after communication failures.
|
|
232
|
+
After successful movement it keeps torque enabled so the arm does not fall.
|
|
233
|
+
Support the arm before running `jolly hardware stop`.
|
|
234
|
+
|
|
235
|
+
The physical benchmark scores measured joint-position error only. The PyBullet
|
|
236
|
+
operator benchmark returns a measured PASS or FAIL without a numeric score.
|
|
237
|
+
Jolly never presents physics output as a physical-hardware result.
|
|
177
238
|
|
|
178
239
|
The bundled SO-101 model is the official Apache-2.0 new-calibration URDF from
|
|
179
240
|
The Robot Studio. Jolly includes the 13 referenced STL meshes from pinned commit
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"motors": {
|
|
3
|
+
"shoulder_pan": {"id": 1, "zero_raw": 2048, "direction": 1, "raw_per_degree": 11.3777777778},
|
|
4
|
+
"shoulder_lift": {"id": 2, "zero_raw": 2048, "direction": 1, "raw_per_degree": 11.3777777778},
|
|
5
|
+
"elbow_flex": {"id": 3, "zero_raw": 2048, "direction": 1, "raw_per_degree": 11.3777777778},
|
|
6
|
+
"wrist_flex": {"id": 4, "zero_raw": 2048, "direction": 1, "raw_per_degree": 11.3777777778},
|
|
7
|
+
"wrist_roll": {"id": 5, "zero_raw": 2048, "direction": 1, "raw_per_degree": 11.3777777778},
|
|
8
|
+
"gripper": {"id": 6, "open_raw": 2048, "closed_raw": 3072}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -23,12 +23,30 @@ jolly scene load blocks --json
|
|
|
23
23
|
jolly challenge list --json
|
|
24
24
|
jolly challenge start sort-red --model jolly6 [--seed N] --json
|
|
25
25
|
jolly challenge status --json
|
|
26
|
-
jolly benchmark [--seed N] [--
|
|
26
|
+
jolly benchmark start [--seed N] [--model so101] --json
|
|
27
|
+
# Issue explicit jolly reach or jolly move controls.
|
|
28
|
+
jolly benchmark score --json
|
|
27
29
|
```
|
|
28
30
|
|
|
29
31
|
Challenge starts use a fresh random seed by default. The JSON response contains
|
|
30
32
|
the seed and generated instance. Supply `--seed N` to replay the exact case.
|
|
31
|
-
|
|
33
|
+
Benchmark start generates a randomized drop-in-hole task through `JollyDriver`
|
|
34
|
+
without moving the robot. Benchmark score measures only the state produced by
|
|
35
|
+
the intervening explicit controls and returns PASS or FAIL without a numeric
|
|
36
|
+
score.
|
|
37
|
+
|
|
38
|
+
## Physical SO-101
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
jolly hardware scan --port /dev/ttyACM0 --calibration calibration.json --json
|
|
42
|
+
jolly hardware state --port /dev/ttyACM0 --calibration calibration.json --json
|
|
43
|
+
jolly hardware move --port /dev/ttyACM0 --calibration calibration.json --joints "0,0,0,0,0" --gripper 0 --confirm-hardware --json
|
|
44
|
+
jolly hardware benchmark --port /dev/ttyACM0 --calibration calibration.json --seed 42 --cases 3 --confirm-hardware --json
|
|
45
|
+
jolly hardware stop --port /dev/ttyACM0 --calibration calibration.json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
PyBullet and physical-hardware results use separate benchmark names. The
|
|
49
|
+
PyBullet operator benchmark has no numeric score.
|
|
32
50
|
|
|
33
51
|
## Viewers
|
|
34
52
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Challenges and Benchmarks
|
|
2
|
+
|
|
3
|
+
Jolly includes local scene templates:
|
|
4
|
+
|
|
5
|
+
- `empty`: clear workspace for kinematics.
|
|
6
|
+
- `blocks`: three blocks and two target pads.
|
|
7
|
+
- `shelf`: floor-to-shelf manipulation.
|
|
8
|
+
- `obstacles`: collision-aware reaching.
|
|
9
|
+
|
|
10
|
+
Agent challenges include `reach-center`, `sort-red`, `shelf-load`, and
|
|
11
|
+
`obstacle-reach`. `JollyDriver` generates a new target and scene layout for each
|
|
12
|
+
start. Each challenge has a command budget and explicit success metrics.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
jolly challenge start sort-red --model jolly6 --json
|
|
16
|
+
# Run move and reach commands.
|
|
17
|
+
jolly challenge status --json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The start result contains a generated seed and instance. Use `--seed` to replay
|
|
21
|
+
the exact case.
|
|
22
|
+
|
|
23
|
+
`jolly benchmark start --json` generates a randomized peg and hole but does not
|
|
24
|
+
move the robot. The operator must issue each `jolly reach` or `jolly move`
|
|
25
|
+
control to approach, descend, grasp, lift, move sideways, lower, and release.
|
|
26
|
+
|
|
27
|
+
`jolly benchmark score --json` does not move the robot and returns no numeric
|
|
28
|
+
score. It returns PASS only when the released peg settles inside the generated
|
|
29
|
+
hole without collision and within the command budget. The result includes the
|
|
30
|
+
full explicit control history and measured final state.
|
|
31
|
+
|
|
32
|
+
Jolly uses its own `JollyEngine`, `JollyDriver`, and direct
|
|
33
|
+
`SO101HardwareDriver`. It does not use Inspect, Inspect AI, or an external robot
|
|
34
|
+
benchmark harness. PyBullet remains the local open-source rigid-body backend
|
|
35
|
+
for physics tasks. Physical SO-101 results come only from STS3215 motor feedback
|
|
36
|
+
and remain separate from the PyBullet PASS or FAIL result.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Physical SO-101 Hardware
|
|
2
|
+
|
|
3
|
+
Install the serial dependency with `pip install 'jolly-cli[hardware]'`.
|
|
4
|
+
|
|
5
|
+
Jolly's `SO101HardwareDriver` sends Feetech STS3215 packets directly at the
|
|
6
|
+
SO-101 default 1,000,000 baud. The configured motor IDs default to the common
|
|
7
|
+
SO-101 sequence of 1 through 6. Each physical arm requires measured calibration.
|
|
8
|
+
|
|
9
|
+
Generate a template, then replace all placeholder zero points, directions, and
|
|
10
|
+
gripper endpoints before movement.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
jolly hardware calibration-example --output calibration.json
|
|
14
|
+
jolly hardware scan --port /dev/ttyACM0 --calibration calibration.json --json
|
|
15
|
+
jolly hardware state --port /dev/ttyACM0 --calibration calibration.json --json
|
|
16
|
+
jolly hardware move --port /dev/ttyACM0 --calibration calibration.json \
|
|
17
|
+
--joints '0,0,0,0,0' --gripper 0 --confirm-hardware --json
|
|
18
|
+
jolly hardware benchmark --port /dev/ttyACM0 --calibration calibration.json \
|
|
19
|
+
--seed 42017 --cases 3 --confirm-hardware --json
|
|
20
|
+
jolly hardware stop --port /dev/ttyACM0 --calibration calibration.json
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The physical benchmark makes bounded movements of at most five degrees from the
|
|
24
|
+
measured starting pose. It scores measured motor-position error and returns to
|
|
25
|
+
the starting pose. Torque stays enabled to hold that pose. Support the arm
|
|
26
|
+
before `jolly hardware stop`. Communication failures trigger a best-effort
|
|
27
|
+
torque disable. Clear the workspace and keep an emergency power cutoff nearby.
|
|
28
|
+
|
|
29
|
+
Physical hardware scores and PyBullet operator PASS or FAIL results are separate. Jolly
|
|
30
|
+
never labels a physics result as a physical-hardware result.
|
|
31
|
+
|
|
32
|
+
Protocol and hardware references:
|
|
33
|
+
|
|
34
|
+
- https://huggingface.co/docs/lerobot/en/so101
|
|
35
|
+
- https://pages.switch-science.com/comparison/files/feetech/serial-sts/STS3215_datasheet.pdf
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Web Viewer
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
[Watch the full WebM recording](../assets/jolly-web-demo.webm) or
|
|
6
|
+
[open the full-resolution still](../assets/jolly-so101-viewer.png).
|
|
7
|
+
|
|
3
8
|
Start the local viewer:
|
|
4
9
|
|
|
5
10
|
```bash
|
|
@@ -11,7 +16,7 @@ Open `http://127.0.0.1:8765`. API documentation is available at
|
|
|
11
16
|
|
|
12
17
|
The viewer provides:
|
|
13
18
|
|
|
14
|
-
- An interactive
|
|
19
|
+
- An interactive PyBullet render of the official SO-101 CAD meshes.
|
|
15
20
|
- Robot and scene selectors.
|
|
16
21
|
- Joint positions and limits.
|
|
17
22
|
- End-effector pose.
|
|
@@ -19,5 +24,8 @@ The viewer provides:
|
|
|
19
24
|
- Scene objects.
|
|
20
25
|
- A safe local command console for state, reset, move, and reach.
|
|
21
26
|
|
|
27
|
+
The recording executes a Cartesian reach in the insertion scene through the
|
|
28
|
+
local command console.
|
|
29
|
+
|
|
22
30
|
The API writes the same persistent state as the CLI. The default bind is
|
|
23
31
|
loopback-only. Do not use `--unsafe-public` on an untrusted network.
|
|
@@ -5,5 +5,6 @@
|
|
|
5
5
|
- [CLI Reference](CLI-Reference)
|
|
6
6
|
- [Robot Models and Licensing](Robot-Models-and-Licensing)
|
|
7
7
|
- [Challenges and Benchmarks](Challenges-and-Benchmarks)
|
|
8
|
+
- [Physical SO-101 Hardware](Physical-SO-101-Hardware)
|
|
8
9
|
- [LLM Agent Safety](LLM-Agent-Safety)
|
|
9
10
|
- [Web Viewer](Web-Viewer)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from jolly.challenges import evaluate
|
|
6
|
+
from jolly.core.errors import ConfigurationError
|
|
7
|
+
|
|
8
|
+
BENCHMARK_ID = "jolly-operator-pick-place-v5"
|
|
9
|
+
BENCHMARK_CHALLENGE = "drop-in-hole"
|
|
10
|
+
BENCHMARK_METADATA_KEYS = (
|
|
11
|
+
"active_challenge",
|
|
12
|
+
"challenge_seed",
|
|
13
|
+
"challenge_instance",
|
|
14
|
+
"active_benchmark",
|
|
15
|
+
"benchmark_controls",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
CONTROL_WORKFLOW = [
|
|
19
|
+
"Inspect the generated peg and hole coordinates in the start result or with 'jolly state --json'.",
|
|
20
|
+
"Move above the peg with 'jolly reach' and an open gripper.",
|
|
21
|
+
"Move down until the gripper contacts the peg, then close the gripper.",
|
|
22
|
+
"Lift the grasped peg.",
|
|
23
|
+
"Move the peg horizontally above the generated hole.",
|
|
24
|
+
"Lower the peg into the opening and release it.",
|
|
25
|
+
"Run 'jolly benchmark score' to measure the final physical state.",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def benchmark_control_history(
|
|
30
|
+
previous: dict[str, object],
|
|
31
|
+
*,
|
|
32
|
+
source: str,
|
|
33
|
+
command: str,
|
|
34
|
+
requested: dict[str, object],
|
|
35
|
+
result: dict[str, object],
|
|
36
|
+
) -> list[object] | None:
|
|
37
|
+
"""Return the control history with one measured explicit motion appended."""
|
|
38
|
+
if previous.get("active_benchmark") != BENCHMARK_ID:
|
|
39
|
+
return None
|
|
40
|
+
existing = previous.get("benchmark_controls", [])
|
|
41
|
+
controls = list(existing) if isinstance(existing, list) else []
|
|
42
|
+
controls.append(
|
|
43
|
+
{
|
|
44
|
+
"index": len(controls) + 1,
|
|
45
|
+
"source": source,
|
|
46
|
+
"command": command,
|
|
47
|
+
"requested": requested,
|
|
48
|
+
"measured": {
|
|
49
|
+
"tool_position": result["end_effector"]["position"],
|
|
50
|
+
"gripper": result["gripper"],
|
|
51
|
+
"held_object": result["held_object"],
|
|
52
|
+
"collision": result["collisions"]["collision"],
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
return controls
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def score_operator_benchmark(state: dict[str, Any]) -> dict[str, object]:
|
|
60
|
+
"""Measure an operator-controlled pick-and-place attempt without moving the robot."""
|
|
61
|
+
if state.get("active_benchmark") != BENCHMARK_ID:
|
|
62
|
+
raise ConfigurationError("No operator benchmark is active. Run 'jolly benchmark start'.")
|
|
63
|
+
if state.get("active_challenge") != BENCHMARK_CHALLENGE:
|
|
64
|
+
raise ConfigurationError("The active benchmark is not the drop-in-hole pick-and-place task.")
|
|
65
|
+
controls = state.get("benchmark_controls")
|
|
66
|
+
if not isinstance(controls, list):
|
|
67
|
+
raise ConfigurationError("The active benchmark has no control history. Start it again.")
|
|
68
|
+
evaluation = evaluate(BENCHMARK_CHALLENGE, state)
|
|
69
|
+
success = bool(evaluation["success"])
|
|
70
|
+
return {
|
|
71
|
+
"ok": True,
|
|
72
|
+
"benchmark": BENCHMARK_ID,
|
|
73
|
+
"engine": "JollyEngine",
|
|
74
|
+
"physics_backend": "PyBullet",
|
|
75
|
+
"mode": "operator-controlled",
|
|
76
|
+
"seed": evaluation["seed"],
|
|
77
|
+
"outcome": "PASS" if success else "FAIL",
|
|
78
|
+
"success": success,
|
|
79
|
+
"control_count": len(controls),
|
|
80
|
+
"controls": controls,
|
|
81
|
+
"instance": evaluation["instance"],
|
|
82
|
+
"measurements": evaluation["metrics"],
|
|
83
|
+
"score": None,
|
|
84
|
+
"score_basis": "No synthetic numeric score. PASS requires the released peg to settle inside the generated hole without collision and within the command budget.",
|
|
85
|
+
}
|
|
@@ -49,6 +49,14 @@ CHALLENGES: dict[str, Challenge] = {
|
|
|
49
49
|
success="Tool-to-goal error <= 7 cm and no collision.",
|
|
50
50
|
max_commands=10,
|
|
51
51
|
),
|
|
52
|
+
"drop-in-hole": Challenge(
|
|
53
|
+
id="drop-in-hole",
|
|
54
|
+
name="Drop in hole",
|
|
55
|
+
scene="insertion",
|
|
56
|
+
description="Grasp the generated peg and physically release it through the generated opening.",
|
|
57
|
+
success="The peg settles inside the opening below the rim without robot contact.",
|
|
58
|
+
max_commands=12,
|
|
59
|
+
),
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
|
|
@@ -97,11 +105,25 @@ def evaluate(challenge_id: str, state: dict[str, object]) -> dict[str, object]:
|
|
|
97
105
|
inside = bounds["x"][0] <= position[0] <= bounds["x"][1] and bounds["y"][0] <= position[1] <= bounds["y"][1]
|
|
98
106
|
success = inside and position[2] >= bounds["minimum_z"]
|
|
99
107
|
metrics = {"inside_shelf_xy": inside, "cargo_height_meters": position[2]}
|
|
100
|
-
|
|
108
|
+
elif challenge_id == "obstacle-reach":
|
|
101
109
|
goal = instance["target"]
|
|
102
110
|
error = math.dist(ee, goal)
|
|
103
111
|
success = error <= 0.07 and not collision
|
|
104
112
|
metrics = {"goal_error_meters": round(error, 6), "collision_free": not collision}
|
|
113
|
+
else:
|
|
114
|
+
peg = objects["peg"]["position"]
|
|
115
|
+
target = instance["target"]
|
|
116
|
+
error = math.dist(peg[:2], target[:2])
|
|
117
|
+
rim_height = float(instance["rim_height"])
|
|
118
|
+
inside = error <= float(instance["hole_radius"]) and peg[2] < rim_height
|
|
119
|
+
success = inside and state.get("held_object") is None and not collision
|
|
120
|
+
metrics = {
|
|
121
|
+
"target_xy_error_meters": round(error, 6),
|
|
122
|
+
"peg_height_meters": peg[2],
|
|
123
|
+
"below_rim": peg[2] < rim_height,
|
|
124
|
+
"released": state.get("held_object") is None,
|
|
125
|
+
"collision_free": not collision,
|
|
126
|
+
}
|
|
105
127
|
command_count = int(state.get("challenge_commands", 0))
|
|
106
128
|
within_budget = command_count <= challenge.max_commands
|
|
107
129
|
success = success and within_budget
|