urkit 0.2.1__tar.gz → 0.3.2__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.
Files changed (39) hide show
  1. {urkit-0.2.1 → urkit-0.3.2}/PKG-INFO +186 -54
  2. {urkit-0.2.1 → urkit-0.3.2}/README.md +185 -53
  3. {urkit-0.2.1 → urkit-0.3.2}/pyproject.toml +5 -1
  4. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/__init__.py +2 -4
  5. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/__main__.py +8 -2
  6. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/cli/points.py +51 -55
  7. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/cli/teach.py +337 -59
  8. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/config.py +1 -1
  9. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/geometry.py +6 -6
  10. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/gripper/__init__.py +2 -2
  11. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/gripper/base.py +12 -23
  12. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/gripper/digital.py +1 -1
  13. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/gripper/presets.py +2 -2
  14. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/gripper/robotiq.py +9 -3
  15. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/motion.py +15 -9
  16. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/points.py +4 -3
  17. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/robot.py +98 -44
  18. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/telemetry.py +16 -17
  19. {urkit-0.2.1 → urkit-0.3.2}/src/urkit.egg-info/PKG-INFO +186 -54
  20. {urkit-0.2.1 → urkit-0.3.2}/tests/test_robot_integration.py +1 -5
  21. {urkit-0.2.1 → urkit-0.3.2}/setup.cfg +0 -0
  22. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/cli/__init__.py +0 -0
  23. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/cli/colors.py +0 -0
  24. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/cli/connection_monitor.py +0 -0
  25. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/connection.py +0 -0
  26. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/exceptions.py +0 -0
  27. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/gripper/robotiq_preamble.py +0 -0
  28. {urkit-0.2.1 → urkit-0.3.2}/src/urkit/io.py +0 -0
  29. {urkit-0.2.1 → urkit-0.3.2}/src/urkit.egg-info/SOURCES.txt +0 -0
  30. {urkit-0.2.1 → urkit-0.3.2}/src/urkit.egg-info/dependency_links.txt +0 -0
  31. {urkit-0.2.1 → urkit-0.3.2}/src/urkit.egg-info/entry_points.txt +0 -0
  32. {urkit-0.2.1 → urkit-0.3.2}/src/urkit.egg-info/requires.txt +0 -0
  33. {urkit-0.2.1 → urkit-0.3.2}/src/urkit.egg-info/top_level.txt +0 -0
  34. {urkit-0.2.1 → urkit-0.3.2}/tests/test_exceptions.py +0 -0
  35. {urkit-0.2.1 → urkit-0.3.2}/tests/test_geometry.py +0 -0
  36. {urkit-0.2.1 → urkit-0.3.2}/tests/test_gripper.py +0 -0
  37. {urkit-0.2.1 → urkit-0.3.2}/tests/test_gripper_factory.py +0 -0
  38. {urkit-0.2.1 → urkit-0.3.2}/tests/test_gripper_presets.py +0 -0
  39. {urkit-0.2.1 → urkit-0.3.2}/tests/test_points.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urkit
3
- Version: 0.2.1
3
+ Version: 0.3.2
4
4
  Summary: Universal Robots e-Series control toolkit built on ur_rtde
5
5
  Author: URKit Contributors
6
6
  License: MIT
@@ -52,20 +52,26 @@ Built on [`ur_rtde`](https://sdurobotics.gitlab.io/ur_rtde/), it packages the op
52
52
  - [Raw RTDE Access](#raw-rtde-access)
53
53
  - [Connection Lifecycle](#connection-lifecycle)
54
54
  - [Error Handling](#error-handling)
55
+ - [Changelog](#changelog)
55
56
 
56
57
  ---
57
58
 
58
59
  ## Quick Start
59
60
 
60
61
  ```bash
61
- pip install urkit
62
+ pip install -U urkit
62
63
  ```
63
64
 
65
+ The `-U` (upgrade) flag ensures you always get the latest version — this project is in early development and changes frequently.
66
+
64
67
  Requires Python 3.8+ and a Universal Robots e-Series (UR3e to UR30).
65
68
 
66
69
  ### Robot Setup (one-time)
67
70
 
68
- 1. **Network**: `☰` → `System` → `Network`: set the robot's IP and subnet.
71
+ 1. **Network**: `☰` → `System` → `System` → `Network`. Set a static IP on the robot and a matching one on your PC. Both addresses must share the same first three octets (the network), with a different last octet (the host). For example:
72
+ - **Robot**: `172.31.1.42` / Subnet `255.255.255.0`
73
+ - **PC**: `172.31.1.1` / Netmask `255.255.255.0`
74
+ - Verify with `ping 172.31.1.42`. Connect via direct Ethernet cable or a switch.
69
75
  2. **Remote Control**: `☰` → `System` → `Remote Control`: Enable. Press the remote/local button on the pendant.
70
76
  3. **Security**: `☰` → `Security` → `Services`: enable RTDE and disable EtherNet/IP, PROFINET, or MODBUS if they're claiming RTDE registers. Save and restart.
71
77
 
@@ -76,9 +82,9 @@ That's it. No `.urp` files to run, no extra programs needed.
76
82
  If you have a Robotiq gripper, install the **Robotiq Gripper Control** URCap first: download from [robotiq.com/support](https://robotiq.com/support), copy the `.urcap` to a USB drive, and install via `☰` → `Settings` → `System` → `URCaps`.
77
83
 
78
84
  ```python
79
- from urkit import URRobot, ROBOTIQ_2F_85 # or ROBOTIQ_2F_140, ROBOTIQ_HAND_E, or gripper=none
85
+ from urkit import URRobot, ROBOTIQ_HAND_E # or ROBOTIQ_2F_85, ROBOTIQ_2F_140, or gripper=None
80
86
 
81
- robot = URRobot(ip="172.31.1.200", points="points.db", gripper=ROBOTIQ_2F_85)
87
+ robot = URRobot(ip="172.31.1.42", points="points.db", gripper=ROBOTIQ_HAND_E)
82
88
  robot.gripper.activate()
83
89
 
84
90
  robot.move_to("home")
@@ -90,9 +96,9 @@ robot.gripper.open()
90
96
 
91
97
  The typical workflow:
92
98
 
93
- 1. **Teach points** use the CLI to position the robot and save named waypoints.
94
- 2. **Write code** create a robot, move to points by name, apply offsets, run sequences.
95
- 3. **Iterate** add more points, tweak your code, repeat.
99
+ 1. **Teach points.** Use the CLI to position the robot and save named waypoints.
100
+ 2. **Write code.** Create a robot, move to points by name, apply offsets, run sequences.
101
+ 3. **Iterate.** Add more points, tweak your code, repeat.
96
102
 
97
103
  ---
98
104
 
@@ -105,8 +111,8 @@ URKit provides two CLI tools: **teach** for interactive robot control, and **poi
105
111
  The interactive teach pendant for moving the robot, saving points, and checking telemetry:
106
112
 
107
113
  ```bash
108
- urkit teach 172.31.1.200 # with robot IP
109
- urkit teach # reads IP from config.yaml
114
+ urkit teach 172.31.1.42 # with robot IP
115
+ urkit teach # reads IP from config.yaml
110
116
  ```
111
117
 
112
118
  **Flags:**
@@ -121,25 +127,18 @@ urkit teach # reads IP from config.yaml
121
127
  | `--gripper-close-on-high` | Digital polarity: `true` or `false` |
122
128
  | `--points` | Path to `points.db` file (overrides config) |
123
129
  | `--config` | Path to config file (default: `config.yaml` in project root or CWD) |
130
+ | `-e`, `--expert` | Disable safety speed clamping (full speed for goto/tcp-down) |
124
131
  | `-v`, `--verbose` | Show verbose output (debug connection issues) |
125
132
 
126
133
  ### Points Explorer
127
134
 
128
- Browse saved waypoints with real-time search filtering no robot connection needed:
135
+ Browse saved waypoints with real-time search filtering. No robot connection needed:
129
136
 
130
137
  ```bash
131
138
  urkit points # uses default points.db
132
139
  urkit points test_points.db # use specific database
133
140
  ```
134
141
 
135
- **Features:**
136
- - **Type to search** — real-time substring filtering
137
- - **Fuzzy matching** — type `pk` to find `pick_1` (>60% match)
138
- - **Smart sorting** — exact prefix matches first, then substring, then fuzzy
139
- - **Spatial sorting** — points ordered by proximity to "home" point (XYZ distance)
140
- - **Theme-aware** — automatically adapts colors for light/dark terminals
141
- - **Arrow keys** — scroll · **ESC** — quit
142
-
143
142
  ### Key Map
144
143
 
145
144
  All movement and orientation keys support **hold-to-repeat**.
@@ -191,13 +190,16 @@ All movement and orientation keys support **hold-to-repeat**.
191
190
  <tr><td><code>X</code></td><td>Open</td></tr>
192
191
  <tr><td><code>C</code></td><td>Close</td></tr>
193
192
  <tr><td><code>V</code></td><td>Set position (mm)</td></tr>
193
+ <tr><td><code>6</code></td><td>Set speed (0-100)</td></tr>
194
+ <tr><td><code>7</code></td><td>Set force (0-100)</td></tr>
195
+ <tr><td colspan="3">Gripper line shows: `Connected 50.0mm (0%) F=100 S=100`</td></tr>
194
196
  </table>
195
197
  </td>
196
198
  <td align="center" style="width:33%">
197
199
  <table>
198
200
  <tr><th>Key</th><th>Action</th></tr>
199
201
  <tr><td><code>B</code></td><td><strong>Save</strong> current position</td></tr>
200
- <tr><td><code>G</code></td><td>Go to saved point</td></tr>
202
+ <tr><td><code>G</code></td><td>Go to saved point (<code>Space</code> to cancel mid-move, <code>ESC</code> to exit)</td></tr>
201
203
  <tr><td><code>H</code></td><td>Delete saved point</td></tr>
202
204
  <tr><td><code>P</code></td><td>Open points explorer</td></tr>
203
205
  <tr><td><code>R</code></td><td>Rename saved point</td></tr>
@@ -217,6 +219,52 @@ All movement and orientation keys support **hold-to-repeat**.
217
219
  </tr>
218
220
  </table>
219
221
 
222
+ ### Joint Display
223
+
224
+ The teach pendant shows live joint angles alongside TCP position and orientation:
225
+
226
+ ```
227
+ Position X=+0.432 Y=+0.111 Z=+0.227
228
+ Orientation R=+131.3 P=-121.0 Y= +8.0
229
+ Joints J1=+150.0 J2=+020.0 J3=+160.0
230
+ J4=+050.0 J5=-080.0 J6=+157.0
231
+ ```
232
+
233
+ Joint angles color-code proximity to mechanical limits:
234
+
235
+ - **Yellow**: within 10% of joint range (warning)
236
+ - **Red**: within 5% of joint range (danger)
237
+
238
+ UR e-Series joint limits:
239
+
240
+ | Joint | Range | Notes |
241
+ |-------|-------|-------|
242
+ | J1 (shoulder pan) | ±360° | Full rotation |
243
+ | J2 (shoulder lift) | ±360° | Full rotation |
244
+ | J3 (elbow) | ±180° | Physically restricted — shoulder lift gets in the way |
245
+ | J4 (wrist 1) | ±360° | Full rotation |
246
+ | J5 (wrist 2) | ±360° | Full rotation |
247
+ | J6 (wrist 3) | ±360° | Tool flange unlimited rotation |
248
+
249
+ Thresholds scale with each joint's range, so warning zones feel proportional across all joints.
250
+
251
+ ### Safety
252
+
253
+ By default, **Go To** and **TCP Down** movements use a slow velocity (0.125 m/s) so its safer for anyone standing near the robot. The user's speed slider still applies as a global multiplier on top of this.
254
+
255
+ Delta movements (W/S/A/D/Q/E) use step-size-based velocities that scale with the speed slider set by the user.
256
+
257
+ To disable the slow default and use full speed, pass `--expert` or set `expert_mode: true` in your config:
258
+
259
+ ```bash
260
+ urkit teach 172.31.1.42 --expert
261
+ ```
262
+
263
+ ```yaml
264
+ # config.yaml
265
+ expert_mode: true
266
+ ```
267
+
220
268
  ---
221
269
 
222
270
  ## API Reference
@@ -226,14 +274,14 @@ All movement and orientation keys support **hold-to-repeat**.
226
274
  ```python
227
275
  from urkit import URRobot, ROBOTIQ_HAND_E
228
276
 
229
- robot = URRobot(ip="172.31.1.200", points="points.db", gripper=ROBOTIQ_HAND_E)
277
+ robot = URRobot(ip="172.31.1.42", points="points.db", gripper=ROBOTIQ_HAND_E)
230
278
  ```
231
279
 
232
280
  With custom motion defaults:
233
281
 
234
282
  ```python
235
283
  robot = URRobot(
236
- ip="172.31.1.200",
284
+ ip="172.31.1.42",
237
285
  points="points.db",
238
286
  gripper=ROBOTIQ_HAND_E,
239
287
  default_vel=0.5, # m/s
@@ -266,39 +314,41 @@ robot.gripper.open() # fully open (blocking by default)
266
314
  robot.gripper.close() # fully closed, stops on contact
267
315
  robot.gripper.open(wait=False) # non-blocking return
268
316
  robot.gripper.set_position(20) # 20mm open (Robotiq only, 0 = closed)
269
- robot.gripper.set_force(50) # grip force: 0-100
270
- robot.gripper.set_speed(80) # movement speed: 0-100
317
+ robot.gripper.set_force(50) # grip force: 0-100 (Robotiq only)
318
+ robot.gripper.set_speed(80) # movement speed: 0-100 (Robotiq only)
271
319
  ```
272
320
 
273
321
  Override preset values for custom fingers:
274
322
 
275
323
  ```python
276
- robot = URRobot(ip="172.31.1.200", points="points.db", gripper=ROBOTIQ_HAND_E, max_mm=120)
324
+ robot = URRobot(ip="172.31.1.42", points="points.db", gripper=ROBOTIQ_HAND_E, max_mm=120)
277
325
  ```
278
326
 
279
327
  #### Digital I/O Grippers
280
328
 
281
- For suction cups, solenoids, or custom actuators:
329
+ Robotiq grippers use a serial protocol over the robot's RS485 port. If you have a suction cup, solenoid, or any actuator controlled by a single digital output pin, use `DigitalGripperConfig` instead. It just turns that pin on (close) and off (open).
282
330
 
283
331
  ```python
284
332
  from urkit import URRobot, DigitalGripperConfig
285
333
 
286
334
  robot = URRobot(
287
- ip="172.31.1.200",
335
+ ip="172.31.1.42",
288
336
  points="points.db",
289
- gripper=DigitalGripperConfig(pin=3),
337
+ gripper=DigitalGripperConfig(pin=3), # pin 3 goes high = close
290
338
  )
291
339
 
292
- robot.gripper.open() # turn pin off (release)
293
- robot.gripper.close() # turn pin on (grab)
340
+ robot.gripper.open() # turn pin 3 off
341
+ robot.gripper.close() # turn pin 3 on
294
342
  ```
295
343
 
344
+ `set_force()` and `set_speed()` are not available for digital grippers.
345
+
296
346
  ### Points & Motion
297
347
 
298
- The points database is optional create a robot without one and attach later:
348
+ The points database is optional. Create a robot without one and attach later:
299
349
 
300
350
  ```python
301
- robot = URRobot(ip="172.31.1.200")
351
+ robot = URRobot(ip="172.31.1.42")
302
352
  robot.points_db = "points.db"
303
353
  ```
304
354
 
@@ -308,23 +358,50 @@ robot.points_db = "points.db"
308
358
  robot.move_to("pick") # linear move (default)
309
359
  robot.move_to("pick", linear=False) # joint move
310
360
  robot.move_to("pick", vel=1.0, acc=0.5) # override speed
361
+ robot.move_to("pick", asynchronous=True) # non-blocking, returns immediately
311
362
  ```
312
363
 
313
- #### Raw Poses
364
+ - **Linear (moveL):** TCP moves in a straight line. Predictable path, slower near complex orientations.
365
+ - **Joint (moveJ):** Each joint moves simultaneously. Faster, but the TCP follows an arc.
366
+ - **Asynchronous:** When `asynchronous=True`, the move runs in the UR controller's background thread and the method returns immediately.
367
+
368
+ #### Non-Blocking Moves
369
+
370
+ Start a move and do other things while it runs:
314
371
 
315
372
  ```python
316
- robot.move_to([0.5, 0, 0.3, 0, 0, 0]) # [x, y, z, rx, ry, rz]
373
+ robot.move_to("pick", asynchronous=True)
374
+
375
+ # Poll until done
376
+ while robot.is_moving():
377
+ time.sleep(0.01)
378
+
379
+ # Or cancel mid-move
380
+ robot.move_to("pick", asynchronous=True)
381
+ while robot.is_moving():
382
+ if should_cancel:
383
+ robot.stop() # sends stopL + stopJ
384
+ break
385
+ time.sleep(0.01)
317
386
  ```
318
387
 
388
+ **Teach pendant Go To** uses this pattern internally — Space cancels the move and returns to the menu.
389
+
390
+ #### Pose Format
391
+
392
+ A pose is `[x, y, z, rx, ry, rz]`: position in meters and orientation as a **rotation vector** (axis-angle in radians). This is not RPY (roll/pitch/yaw). The teach pendant displays RPY in degrees, which is a different representation. Values you see on the pendant won't match `get_tcp_pose()` directly.
393
+
319
394
  #### Offsets
320
395
 
396
+ Offsets are 6-element lists `[dx, dy, dz, drx, dry, drz]`:
397
+
321
398
  ```python
322
399
  robot.move_to("pick", offset=[0, 0, 0.05, 0, 0, 0]) # 5cm above pick
323
400
  ```
324
401
 
325
402
  #### Resolve a Pose
326
403
 
327
- Get a pose without moving useful for logging, comparisons, or custom motion:
404
+ Get a pose without moving. Useful for logging, comparisons, or custom motion:
328
405
 
329
406
  ```python
330
407
  pose = robot.get_pose("pick")
@@ -338,11 +415,15 @@ robot.move_to(pose) # move to the resolved pose later
338
415
  from urkit import MoveFrame
339
416
 
340
417
  robot.move_frame = MoveFrame.TOOL # default is BASE
341
- robot.move_to("pick", offset=[0, 0, 0.05]) # 5cm along tool Z
418
+ robot.move_relative([0, 0, 0.05, 0, 0, 0]) # 5cm along tool Z
342
419
  ```
343
420
 
344
- - **BASE** (default): offset relative to robot base
345
- - **TOOL**: offset relative to TCP orientation
421
+ - **BASE** (default): delta relative to robot base
422
+ - **TOOL**: delta relative to TCP orientation
423
+
424
+ #### Points are tool-agnostic
425
+
426
+ Points are stored in the active TCP frame, so they work with any tool. If you swap grippers and set the correct TCP offset, your saved points remain valid.
346
427
 
347
428
  #### Point Management
348
429
 
@@ -359,7 +440,7 @@ robot.import_points("backup.json")
359
440
 
360
441
  ```python
361
442
  robot.move_relative([0, 0.01, 0, 0, 0, 0]) # 1cm along Y
362
- robot.move_relative([0, 0, 0.05], frame=MoveFrame.TOOL)
443
+ robot.move_relative([0, 0, 0.05, 0, 0, 0], frame=MoveFrame.TOOL) # 5cm along tool Z
363
444
  ```
364
445
 
365
446
  #### Sequences with Blending
@@ -372,7 +453,7 @@ robot.move_sequence(["a", "b", "c"], blend_radius=0.02)
372
453
  #### Contact Detection
373
454
 
374
455
  ```python
375
- robot.move_until_contact([0, 0, -0.02, 0, 0, 0]) # Ctrl+C to stop
456
+ robot.move_until_contact([0, 0, -0.02, 0, 0, 0])
376
457
  ```
377
458
 
378
459
  #### Velocity Control
@@ -396,11 +477,13 @@ robot.is_freedrive_active # check state
396
477
  #### Speed Control
397
478
 
398
479
  ```python
399
- robot.speed_stop() # emergency stop
400
- robot.set_speed_slider(0.5) # 50% hardware velocity cap
480
+ robot.stop() # stop current move immediately (stopL + stopJ)
481
+ robot.speed_stop() # stop velocity-controlled motion (not E-stop)
482
+ robot.set_speed_slider(0.5) # 50% velocity cap
483
+ robot.get_speed_slider() # read current slider (0.0-1.0)
401
484
  ```
402
485
 
403
- The speed slider is a hardware-level multiplier — same as the physical slider on the pendant. It's global, persistent, and affects all motion commands.
486
+ The speed slider controls the pendant's speed multiplier. It's global, persistent, and affects all motion commands.
404
487
 
405
488
  #### Inverse Kinematics
406
489
 
@@ -415,11 +498,27 @@ pose = robot.get_tcp_pose() # [x, y, z, rx, ry, rz]
415
498
  joints = robot.get_joint_positions() # [j0..j5]
416
499
  force = robot.get_tcp_force() # [fx, fy, fz, mx, my, mz]
417
500
  mode = robot.get_robot_mode() # "REMOTE_CONTROL", "SERVOING", etc.
418
- scaling = robot.get_speed_scaling() # 0.0-1.0
419
501
  payload = robot.get_payload() # kg
502
+ robot.is_moving() # bool — any joint/TCP velocity non-zero
420
503
  robot.is_protective_stopped() # bool
421
504
  robot.is_emergency_stopped() # bool
422
505
  robot.current_point() # {"pose": [...], "joints": [...]}
506
+ robot.is_at_pose(target) # bool — TCP within 1mm / 0.5°
507
+ robot.is_at_joints(target) # bool — all joints within 0.001 rad
508
+ ```
509
+
510
+ `is_moving()` is the simplest way to poll — `is_at_pose()` / `is_at_joints()` for precise target checking:
511
+
512
+ ```python
513
+ robot.move_to("pick", asynchronous=True)
514
+
515
+ # Simple: wait until robot stops
516
+ while robot.is_moving():
517
+ time.sleep(0.01)
518
+
519
+ # Precise: check against specific target
520
+ while not robot.is_at_pose(target_pose):
521
+ time.sleep(0.01)
423
522
  ```
424
523
 
425
524
  ### Digital I/O
@@ -453,11 +552,12 @@ URKit searches for `config.yaml` in this order:
453
552
 
454
553
  | Key | Description | Example |
455
554
  |-----|-------------|---------|
456
- | `robot_ip` | Robot IP address | `192.168.1.100` |
555
+ | `robot_ip` | Robot IP address | `172.31.1.42` |
457
556
  | `points_path` | Path to SQLite points database | `points.db` |
458
557
  | `gripper` | Gripper preset name | `hand-e`, `2f-85`, `2f-140`, `digital` |
459
558
  | `default_vel` | Default linear velocity (m/s) | `0.5` |
460
559
  | `default_acc` | Default linear acceleration (m/s²) | `0.3` |
560
+ | `expert_mode` | Disable safety speed clamping | `false` |
461
561
 
462
562
  ### Gripper Config
463
563
 
@@ -477,17 +577,17 @@ gripper_config:
477
577
 
478
578
  ### CLI Override Precedence
479
579
 
480
- 1. **CLI flags** `urkit teach 172.31.1.200 --gripper none`
481
- 2. **Config file** values from `config.yaml`
482
- 3. **Built-in defaults** `points.db`, no gripper, 0.5 m/s velocity
580
+ 1. **CLI flags.** `urkit teach 172.31.1.42 --gripper none`
581
+ 2. **Config file.** Values from `config.yaml`
582
+ 3. **Built-in defaults.** `points.db`, no gripper, 0.5 m/s velocity
483
583
 
484
584
  ### Saving Config
485
585
 
486
586
  The CLI **never** modifies your config file automatically. Press **Y** inside the teach pendant to save. This way you only save settings you've actually tested.
487
587
 
488
588
  ```bash
489
- urkit teach 172.31.1.200 --gripper hand-e # test, then press Y
490
- urkit teach # next time: reads from config
589
+ urkit teach 172.31.1.42 --gripper hand-e # test, then press Y
590
+ urkit teach # next time: reads from config
491
591
  ```
492
592
 
493
593
  Multiple workcells:
@@ -505,7 +605,7 @@ from urkit import load_config, resolve_config
505
605
  config = load_config() # auto-resolve
506
606
  config = load_config("/path/to/my.yaml") # explicit path
507
607
  path = resolve_config() # returns Path or None
508
- robot = URRobot.from_config({"robot_ip": "172.31.1.200", "gripper": "2f-85"})
608
+ robot = URRobot.from_config({"robot_ip": "172.31.1.42", "gripper": "2f-85"})
509
609
  ```
510
610
 
511
611
  ---
@@ -517,7 +617,6 @@ robot = URRobot.from_config({"robot_ip": "172.31.1.200", "gripper": "2f-85"})
517
617
  URKit doesn't try to wrap everything. Access the raw `ur_rtde` interfaces for advanced features:
518
618
 
519
619
  ```python
520
- robot.rtde_control.moveUntilContact([0, 0, -0.02, 0, 0, 0])
521
620
  robot.rtde_control.forceMode(...)
522
621
  robot.rtde_control.servoJ(...)
523
622
  robot.rtde_receive.getActualCurrent()
@@ -537,10 +636,10 @@ robot.reconnect_rtde() # reconnect after a drop
537
636
  ### Error Handling
538
637
 
539
638
  ```python
540
- from urkit import URKitError, RobotNotInRemoteModeError, RtdeRegisterConflictError
639
+ from urkit import URKitError, MotionError, PointError
541
640
 
542
641
  try:
543
- robot = URRobot(ip="172.31.1.200", points="points.db")
642
+ robot = URRobot(ip="172.31.1.42", points="points.db")
544
643
  except RobotNotInRemoteModeError:
545
644
  print("Enable remote control on the teach pendant!")
546
645
  except RtdeRegisterConflictError:
@@ -548,3 +647,36 @@ except RtdeRegisterConflictError:
548
647
  except URKitError as e:
549
648
  print(f"Error: {e}")
550
649
  ```
650
+
651
+ Common runtime errors:
652
+
653
+ | Exception | When |
654
+ |-----------|------|
655
+ | `MotionError` | Unreachable pose, bad TCP offset, freedrive failure |
656
+ | `PointError` | Point not found, no points database configured |
657
+ | `GripperError` | Gripper activation or communication failure |
658
+ | `URKitIOError` | Invalid pin number, I/O read/write failure |
659
+ | `TelemetryError` | Cannot read pose, joints, force, etc. |
660
+
661
+ When the robot enters protective stop or the RTDE connection drops, motion commands raise `URKitConnectionError` and the program should exit. The CLI handles this automatically.
662
+
663
+ ### Connection Notes
664
+
665
+ The `URRobot` constructor takes a few seconds on first call: it validates the connection, checks remote mode, powers on the robot, releases brakes, and connects RTDE. Subsequent calls are faster if the robot is already running.
666
+
667
+ ---
668
+
669
+ ## Changelog
670
+
671
+ See [CHANGELOG.md](CHANGELOG.md) for the full version history.
672
+
673
+ ### Recent Changes
674
+
675
+ - **v3.2** — Gripper speed/force keys (6/7), force/speed display
676
+ - **v3.1** — Async motion, Space cancel, progress bar, joint display
677
+ - **v0.3.0** — Safe/expert mode, speed_stop on exit, safety features
678
+ - **v0.2.1** — `get_pose()` method
679
+ - **v0.2.0** — README restructure
680
+ - **v0.1.1** — `--gripper none`, `--config` flag, save config on demand
681
+ - **v0.1.0** — Initial release
682
+