urkit 0.1.1__tar.gz → 0.2.1__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 (42) hide show
  1. urkit-0.2.1/PKG-INFO +550 -0
  2. urkit-0.2.1/README.md +524 -0
  3. {urkit-0.1.1 → urkit-0.2.1}/pyproject.toml +1 -1
  4. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/robot.py +43 -0
  5. urkit-0.2.1/src/urkit.egg-info/PKG-INFO +550 -0
  6. urkit-0.1.1/PKG-INFO +0 -707
  7. urkit-0.1.1/README.md +0 -681
  8. urkit-0.1.1/src/urkit.egg-info/PKG-INFO +0 -707
  9. {urkit-0.1.1 → urkit-0.2.1}/setup.cfg +0 -0
  10. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/__init__.py +0 -0
  11. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/__main__.py +0 -0
  12. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/cli/__init__.py +0 -0
  13. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/cli/colors.py +0 -0
  14. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/cli/connection_monitor.py +0 -0
  15. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/cli/points.py +0 -0
  16. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/cli/teach.py +0 -0
  17. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/config.py +0 -0
  18. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/connection.py +0 -0
  19. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/exceptions.py +0 -0
  20. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/geometry.py +0 -0
  21. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/gripper/__init__.py +0 -0
  22. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/gripper/base.py +0 -0
  23. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/gripper/digital.py +0 -0
  24. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/gripper/presets.py +0 -0
  25. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/gripper/robotiq.py +0 -0
  26. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/gripper/robotiq_preamble.py +0 -0
  27. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/io.py +0 -0
  28. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/motion.py +0 -0
  29. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/points.py +0 -0
  30. {urkit-0.1.1 → urkit-0.2.1}/src/urkit/telemetry.py +0 -0
  31. {urkit-0.1.1 → urkit-0.2.1}/src/urkit.egg-info/SOURCES.txt +0 -0
  32. {urkit-0.1.1 → urkit-0.2.1}/src/urkit.egg-info/dependency_links.txt +0 -0
  33. {urkit-0.1.1 → urkit-0.2.1}/src/urkit.egg-info/entry_points.txt +0 -0
  34. {urkit-0.1.1 → urkit-0.2.1}/src/urkit.egg-info/requires.txt +0 -0
  35. {urkit-0.1.1 → urkit-0.2.1}/src/urkit.egg-info/top_level.txt +0 -0
  36. {urkit-0.1.1 → urkit-0.2.1}/tests/test_exceptions.py +0 -0
  37. {urkit-0.1.1 → urkit-0.2.1}/tests/test_geometry.py +0 -0
  38. {urkit-0.1.1 → urkit-0.2.1}/tests/test_gripper.py +0 -0
  39. {urkit-0.1.1 → urkit-0.2.1}/tests/test_gripper_factory.py +0 -0
  40. {urkit-0.1.1 → urkit-0.2.1}/tests/test_gripper_presets.py +0 -0
  41. {urkit-0.1.1 → urkit-0.2.1}/tests/test_points.py +0 -0
  42. {urkit-0.1.1 → urkit-0.2.1}/tests/test_robot_integration.py +0 -0
urkit-0.2.1/PKG-INFO ADDED
@@ -0,0 +1,550 @@
1
+ Metadata-Version: 2.4
2
+ Name: urkit
3
+ Version: 0.2.1
4
+ Summary: Universal Robots e-Series control toolkit built on ur_rtde
5
+ Author: URKit Contributors
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/rodolfo-verde/urkit
8
+ Keywords: universal-robots,ur3e,ur5e,ur7e,ur10e,ur12e,ur16e,ur8,ur15,ur18,ur20,ur30,rtde,robotics,industrial-robot
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Topic :: Scientific/Engineering :: Human Machine Interfaces
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: ur_rtde>=1.4.0
23
+ Requires-Dist: pyyaml>=6.0
24
+ Requires-Dist: colorama>=0.4.6
25
+ Requires-Dist: rich>=13.0.0
26
+
27
+ # URKit
28
+
29
+ [![PyPI](https://img.shields.io/pypi/v/urkit.svg)](https://pypi.org/project/urkit/)
30
+
31
+ **URKit** is a Python toolkit for [Universal Robots](https://www.universal-robots.com/) e-Series robots that makes the common stuff simple and gets out of the way for everything else.
32
+
33
+ Built on [`ur_rtde`](https://sdurobotics.gitlab.io/ur_rtde/), it packages the operations you reach for most: connecting, moving to named points, gripper control, telemetry, and I/O, while exposing the raw RTDE interfaces for anything deeper.
34
+
35
+ ---
36
+
37
+ ## Table of Contents
38
+
39
+ - [Quick Start](#quick-start)
40
+ - [Interactive CLI](#interactive-cli)
41
+ - [Teach Mode](#teach-mode)
42
+ - [Points Explorer](#points-explorer)
43
+ - [Key Map](#key-map)
44
+ - [API Reference](#api-reference)
45
+ - [Connecting](#connecting)
46
+ - [Grippers](#grippers)
47
+ - [Points & Motion](#points--motion)
48
+ - [Telemetry](#telemetry)
49
+ - [Digital I/O](#digital-io)
50
+ - [Configuration](#configuration)
51
+ - [Advanced](#advanced)
52
+ - [Raw RTDE Access](#raw-rtde-access)
53
+ - [Connection Lifecycle](#connection-lifecycle)
54
+ - [Error Handling](#error-handling)
55
+
56
+ ---
57
+
58
+ ## Quick Start
59
+
60
+ ```bash
61
+ pip install urkit
62
+ ```
63
+
64
+ Requires Python 3.8+ and a Universal Robots e-Series (UR3e to UR30).
65
+
66
+ ### Robot Setup (one-time)
67
+
68
+ 1. **Network**: `☰` → `System` → `Network`: set the robot's IP and subnet.
69
+ 2. **Remote Control**: `☰` → `System` → `Remote Control`: Enable. Press the remote/local button on the pendant.
70
+ 3. **Security**: `☰` → `Security` → `Services`: enable RTDE and disable EtherNet/IP, PROFINET, or MODBUS if they're claiming RTDE registers. Save and restart.
71
+
72
+ That's it. No `.urp` files to run, no extra programs needed.
73
+
74
+ ### Example
75
+
76
+ 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
+
78
+ ```python
79
+ from urkit import URRobot, ROBOTIQ_2F_85 # or ROBOTIQ_2F_140, ROBOTIQ_HAND_E, or gripper=none
80
+
81
+ robot = URRobot(ip="172.31.1.200", points="points.db", gripper=ROBOTIQ_2F_85)
82
+ robot.gripper.activate()
83
+
84
+ robot.move_to("home")
85
+ robot.move_to("pick", offset=[0, 0, 0.05, 0, 0, 0])
86
+ robot.gripper.close()
87
+ robot.move_to("place")
88
+ robot.gripper.open()
89
+ ```
90
+
91
+ The typical workflow:
92
+
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.
96
+
97
+ ---
98
+
99
+ ## Interactive CLI
100
+
101
+ URKit provides two CLI tools: **teach** for interactive robot control, and **points** for browsing saved waypoints.
102
+
103
+ ### Teach Mode
104
+
105
+ The interactive teach pendant for moving the robot, saving points, and checking telemetry:
106
+
107
+ ```bash
108
+ urkit teach 172.31.1.200 # with robot IP
109
+ urkit teach # reads IP from config.yaml
110
+ ```
111
+
112
+ **Flags:**
113
+
114
+ | Flag | Description |
115
+ |------|-------------|
116
+ | `ip` | Robot IP address (positional, overrides config) |
117
+ | `--gripper` | Gripper preset: `2f-85`, `2f-140`, `hand-e`, `digital`, `none` |
118
+ | `--gripper-pin` | Digital gripper output pin (default: 0) |
119
+ | `--gripper-force` | Robotiq force 0-100 |
120
+ | `--gripper-speed` | Robotiq speed 0-100 |
121
+ | `--gripper-close-on-high` | Digital polarity: `true` or `false` |
122
+ | `--points` | Path to `points.db` file (overrides config) |
123
+ | `--config` | Path to config file (default: `config.yaml` in project root or CWD) |
124
+ | `-v`, `--verbose` | Show verbose output (debug connection issues) |
125
+
126
+ ### Points Explorer
127
+
128
+ Browse saved waypoints with real-time search filtering — no robot connection needed:
129
+
130
+ ```bash
131
+ urkit points # uses default points.db
132
+ urkit points test_points.db # use specific database
133
+ ```
134
+
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
+ ### Key Map
144
+
145
+ All movement and orientation keys support **hold-to-repeat**.
146
+
147
+ <table>
148
+ <tr>
149
+ <th align="center">Movement</th>
150
+ <th align="center">Orientation</th>
151
+ <th align="center">Step Size</th>
152
+ </tr>
153
+ <tr>
154
+ <td align="center" style="width:33%">
155
+ <table>
156
+ <tr><th>Key</th><th>Action</th></tr>
157
+ <tr><td><code>W</code> / <code>S</code></td><td>+X / -X</td></tr>
158
+ <tr><td><code>A</code> / <code>D</code></td><td>+Y / -Y</td></tr>
159
+ <tr><td><code>Q</code> / <code>E</code></td><td>+Z / -Z</td></tr>
160
+ </table>
161
+ </td>
162
+ <td align="center" style="width:33%">
163
+ <table>
164
+ <tr><th>Key</th><th>Action</th></tr>
165
+ <tr><td><code>U</code> / <code>O</code></td><td>Roll - / +</td></tr>
166
+ <tr><td><code>I</code> / <code>K</code></td><td>Pitch - / +</td></tr>
167
+ <tr><td><code>J</code> / <code>L</code></td><td>Yaw - / +</td></tr>
168
+ </table>
169
+ </td>
170
+ <td align="center" style="width:34%">
171
+ <table>
172
+ <tr><th>Key</th><th>Action</th></tr>
173
+ <tr><td><code>1</code></td><td>Set linear step (mm)</td></tr>
174
+ <tr><td><code>2</code></td><td>Set angular step (deg)</td></tr>
175
+ <tr><td><code>.</code></td><td>Reset defaults (5 mm / 2°)</td></tr>
176
+ </table>
177
+ </td>
178
+ </tr>
179
+ </table>
180
+
181
+ <table>
182
+ <tr>
183
+ <th align="center">Gripper</th>
184
+ <th align="center">Points</th>
185
+ <th align="center">Controls</th>
186
+ </tr>
187
+ <tr>
188
+ <td align="center" style="width:33%">
189
+ <table>
190
+ <tr><th>Key</th><th>Action</th></tr>
191
+ <tr><td><code>X</code></td><td>Open</td></tr>
192
+ <tr><td><code>C</code></td><td>Close</td></tr>
193
+ <tr><td><code>V</code></td><td>Set position (mm)</td></tr>
194
+ </table>
195
+ </td>
196
+ <td align="center" style="width:33%">
197
+ <table>
198
+ <tr><th>Key</th><th>Action</th></tr>
199
+ <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>
201
+ <tr><td><code>H</code></td><td>Delete saved point</td></tr>
202
+ <tr><td><code>P</code></td><td>Open points explorer</td></tr>
203
+ <tr><td><code>R</code></td><td>Rename saved point</td></tr>
204
+ </table>
205
+ </td>
206
+ <td align="center" style="width:34%">
207
+ <table>
208
+ <tr><th>Key</th><th>Action</th></tr>
209
+ <tr><td><code>F</code></td><td>Freedrive (OFF → ALL → XYZ+Rz)</td></tr>
210
+ <tr><td><code>M</code></td><td>Toggle frame (BASE / TOOL)</td></tr>
211
+ <tr><td><code>N</code></td><td>Go To mode (Cartesian / Joint)</td></tr>
212
+ <tr><td><code>T</code></td><td>Orient TCP down (180°)</td></tr>
213
+ <tr><td><code>Y</code></td><td>Save config to file</td></tr>
214
+ <tr><td><code>ESC</code></td><td>Exit</td></tr>
215
+ </table>
216
+ </td>
217
+ </tr>
218
+ </table>
219
+
220
+ ---
221
+
222
+ ## API Reference
223
+
224
+ ### Connecting
225
+
226
+ ```python
227
+ from urkit import URRobot, ROBOTIQ_HAND_E
228
+
229
+ robot = URRobot(ip="172.31.1.200", points="points.db", gripper=ROBOTIQ_HAND_E)
230
+ ```
231
+
232
+ With custom motion defaults:
233
+
234
+ ```python
235
+ robot = URRobot(
236
+ ip="172.31.1.200",
237
+ points="points.db",
238
+ gripper=ROBOTIQ_HAND_E,
239
+ default_vel=0.5, # m/s
240
+ default_acc=0.3, # m/s²
241
+ )
242
+ ```
243
+
244
+ From a config file:
245
+
246
+ ```python
247
+ robot = URRobot.from_config("config.yaml")
248
+ robot = URRobot.from_config("config.yaml", ip="10.0.0.50") # override IP
249
+ ```
250
+
251
+ ### Grippers
252
+
253
+ Three built-in presets:
254
+
255
+ | Preset | Description |
256
+ |--------|-------------|
257
+ | `ROBOTIQ_HAND_E` | Robotiq 2F-140-E (Hand-E series) |
258
+ | `ROBOTIQ_2F_85` | Robotiq 2F-85 |
259
+ | `ROBOTIQ_2F_140` | Robotiq 2F-140 |
260
+
261
+ ```python
262
+ robot.gripper.activate() # required before open/close (Robotiq only)
263
+ robot.gripper.is_activated() # check activation state
264
+
265
+ robot.gripper.open() # fully open (blocking by default)
266
+ robot.gripper.close() # fully closed, stops on contact
267
+ robot.gripper.open(wait=False) # non-blocking return
268
+ 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
271
+ ```
272
+
273
+ Override preset values for custom fingers:
274
+
275
+ ```python
276
+ robot = URRobot(ip="172.31.1.200", points="points.db", gripper=ROBOTIQ_HAND_E, max_mm=120)
277
+ ```
278
+
279
+ #### Digital I/O Grippers
280
+
281
+ For suction cups, solenoids, or custom actuators:
282
+
283
+ ```python
284
+ from urkit import URRobot, DigitalGripperConfig
285
+
286
+ robot = URRobot(
287
+ ip="172.31.1.200",
288
+ points="points.db",
289
+ gripper=DigitalGripperConfig(pin=3),
290
+ )
291
+
292
+ robot.gripper.open() # turn pin off (release)
293
+ robot.gripper.close() # turn pin on (grab)
294
+ ```
295
+
296
+ ### Points & Motion
297
+
298
+ The points database is optional — create a robot without one and attach later:
299
+
300
+ ```python
301
+ robot = URRobot(ip="172.31.1.200")
302
+ robot.points_db = "points.db"
303
+ ```
304
+
305
+ #### Moving to Points
306
+
307
+ ```python
308
+ robot.move_to("pick") # linear move (default)
309
+ robot.move_to("pick", linear=False) # joint move
310
+ robot.move_to("pick", vel=1.0, acc=0.5) # override speed
311
+ ```
312
+
313
+ #### Raw Poses
314
+
315
+ ```python
316
+ robot.move_to([0.5, 0, 0.3, 0, 0, 0]) # [x, y, z, rx, ry, rz]
317
+ ```
318
+
319
+ #### Offsets
320
+
321
+ ```python
322
+ robot.move_to("pick", offset=[0, 0, 0.05, 0, 0, 0]) # 5cm above pick
323
+ ```
324
+
325
+ #### Resolve a Pose
326
+
327
+ Get a pose without moving — useful for logging, comparisons, or custom motion:
328
+
329
+ ```python
330
+ pose = robot.get_pose("pick")
331
+ pose = robot.get_pose("pick", offset=[0, 0, 0.05, 0, 0, 0]) # with offset
332
+ robot.move_to(pose) # move to the resolved pose later
333
+ ```
334
+
335
+ #### Coordinate Frame
336
+
337
+ ```python
338
+ from urkit import MoveFrame
339
+
340
+ robot.move_frame = MoveFrame.TOOL # default is BASE
341
+ robot.move_to("pick", offset=[0, 0, 0.05]) # 5cm along tool Z
342
+ ```
343
+
344
+ - **BASE** (default): offset relative to robot base
345
+ - **TOOL**: offset relative to TCP orientation
346
+
347
+ #### Point Management
348
+
349
+ ```python
350
+ robot.save_point("here")
351
+ robot.point_names() # ["home", "pick", "place"]
352
+ robot.rename_point("old", "new")
353
+ robot.delete_point("old")
354
+ robot.export_points("backup.json")
355
+ robot.import_points("backup.json")
356
+ ```
357
+
358
+ #### Relative Moves
359
+
360
+ ```python
361
+ robot.move_relative([0, 0.01, 0, 0, 0, 0]) # 1cm along Y
362
+ robot.move_relative([0, 0, 0.05], frame=MoveFrame.TOOL)
363
+ ```
364
+
365
+ #### Sequences with Blending
366
+
367
+ ```python
368
+ robot.move_sequence(["a", "b", "c"])
369
+ robot.move_sequence(["a", "b", "c"], blend_radius=0.02)
370
+ ```
371
+
372
+ #### Contact Detection
373
+
374
+ ```python
375
+ robot.move_until_contact([0, 0, -0.02, 0, 0, 0]) # Ctrl+C to stop
376
+ ```
377
+
378
+ #### Velocity Control
379
+
380
+ ```python
381
+ robot.move_velocity([0, 0, -0.02, 0, 0, 0], duration=1.0)
382
+ ```
383
+
384
+ #### Freedrive Mode
385
+
386
+ ```python
387
+ from urkit import FreedriveMode
388
+
389
+ robot.enable_freedrive() # all 6 axes free
390
+ robot.enable_freedrive(FreedriveMode.XYZ) # linear axes + Rz rotation
391
+ robot.enable_freedrive(FreedriveMode.ROTATION) # rotation only
392
+ robot.disable_freedrive() # disable before sending motion commands
393
+ robot.is_freedrive_active # check state
394
+ ```
395
+
396
+ #### Speed Control
397
+
398
+ ```python
399
+ robot.speed_stop() # emergency stop
400
+ robot.set_speed_slider(0.5) # 50% hardware velocity cap
401
+ ```
402
+
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.
404
+
405
+ #### Inverse Kinematics
406
+
407
+ ```python
408
+ joints = robot.inverse_kinematics([0.5, 0, 0.3, 0, 0, 0])
409
+ ```
410
+
411
+ ### Telemetry
412
+
413
+ ```python
414
+ pose = robot.get_tcp_pose() # [x, y, z, rx, ry, rz]
415
+ joints = robot.get_joint_positions() # [j0..j5]
416
+ force = robot.get_tcp_force() # [fx, fy, fz, mx, my, mz]
417
+ mode = robot.get_robot_mode() # "REMOTE_CONTROL", "SERVOING", etc.
418
+ scaling = robot.get_speed_scaling() # 0.0-1.0
419
+ payload = robot.get_payload() # kg
420
+ robot.is_protective_stopped() # bool
421
+ robot.is_emergency_stopped() # bool
422
+ robot.current_point() # {"pose": [...], "joints": [...]}
423
+ ```
424
+
425
+ ### Digital I/O
426
+
427
+ ```python
428
+ robot.set_digital_output(0, True)
429
+ robot.set_digital_outputs({0: True, 1: False, 8: True})
430
+ robot.set_digital_outputs(False) # clear all
431
+
432
+ robot.get_digital_input(0)
433
+ robot.get_analog_input(0)
434
+ robot.get_tool_input(0)
435
+
436
+ robot.wait_for_input(0, True, timeout=10.0) # block until pin 0 goes high
437
+ ```
438
+
439
+ ---
440
+
441
+ ## Configuration
442
+
443
+ URKit uses a YAML config file (`config.yaml`) to persist settings between sessions.
444
+
445
+ ### Location
446
+
447
+ URKit searches for `config.yaml` in this order:
448
+ 1. Explicit path via `--config` flag or `load_config("path")`
449
+ 2. Project root (where `src/urkit` lives)
450
+ 3. Current working directory
451
+
452
+ ### Keys
453
+
454
+ | Key | Description | Example |
455
+ |-----|-------------|---------|
456
+ | `robot_ip` | Robot IP address | `192.168.1.100` |
457
+ | `points_path` | Path to SQLite points database | `points.db` |
458
+ | `gripper` | Gripper preset name | `hand-e`, `2f-85`, `2f-140`, `digital` |
459
+ | `default_vel` | Default linear velocity (m/s) | `0.5` |
460
+ | `default_acc` | Default linear acceleration (m/s²) | `0.3` |
461
+
462
+ ### Gripper Config
463
+
464
+ ```yaml
465
+ gripper: digital
466
+ gripper_config:
467
+ pin: 3
468
+ close_on_high: true
469
+ ```
470
+
471
+ ```yaml
472
+ gripper: hand-e
473
+ gripper_config:
474
+ force: 50
475
+ speed: 80
476
+ ```
477
+
478
+ ### CLI Override Precedence
479
+
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
483
+
484
+ ### Saving Config
485
+
486
+ 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
+
488
+ ```bash
489
+ urkit teach 172.31.1.200 --gripper hand-e # test, then press Y
490
+ urkit teach # next time: reads from config
491
+ ```
492
+
493
+ Multiple workcells:
494
+
495
+ ```bash
496
+ urkit teach --config station_a.yaml # press Y to save
497
+ urkit teach --config station_b.yaml # separate config
498
+ ```
499
+
500
+ ### Programmatic
501
+
502
+ ```python
503
+ from urkit import load_config, resolve_config
504
+
505
+ config = load_config() # auto-resolve
506
+ config = load_config("/path/to/my.yaml") # explicit path
507
+ path = resolve_config() # returns Path or None
508
+ robot = URRobot.from_config({"robot_ip": "172.31.1.200", "gripper": "2f-85"})
509
+ ```
510
+
511
+ ---
512
+
513
+ ## Advanced
514
+
515
+ ### Raw RTDE Access
516
+
517
+ URKit doesn't try to wrap everything. Access the raw `ur_rtde` interfaces for advanced features:
518
+
519
+ ```python
520
+ robot.rtde_control.moveUntilContact([0, 0, -0.02, 0, 0, 0])
521
+ robot.rtde_control.forceMode(...)
522
+ robot.rtde_control.servoJ(...)
523
+ robot.rtde_receive.getActualCurrent()
524
+ ```
525
+
526
+ Full `ur_rtde` documentation: <https://sdurobotics.gitlab.io/ur_rtde/>
527
+
528
+ ### Connection Monitoring
529
+
530
+ ```python
531
+ robot.connection_lost # bool: check if RTDE dropped
532
+ robot.reconnect_rtde() # reconnect after a drop
533
+ ```
534
+
535
+ `disconnect()` is called automatically when the robot object is garbage collected.
536
+
537
+ ### Error Handling
538
+
539
+ ```python
540
+ from urkit import URKitError, RobotNotInRemoteModeError, RtdeRegisterConflictError
541
+
542
+ try:
543
+ robot = URRobot(ip="172.31.1.200", points="points.db")
544
+ except RobotNotInRemoteModeError:
545
+ print("Enable remote control on the teach pendant!")
546
+ except RtdeRegisterConflictError:
547
+ print("Disable EtherNet/IP, PROFINET, or MODBUS!")
548
+ except URKitError as e:
549
+ print(f"Error: {e}")
550
+ ```