urkit 0.1.1__tar.gz → 0.2.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.
Files changed (42) hide show
  1. urkit-0.2.0/PKG-INFO +541 -0
  2. urkit-0.2.0/README.md +515 -0
  3. {urkit-0.1.1 → urkit-0.2.0}/pyproject.toml +1 -1
  4. urkit-0.2.0/src/urkit.egg-info/PKG-INFO +541 -0
  5. urkit-0.1.1/PKG-INFO +0 -707
  6. urkit-0.1.1/README.md +0 -681
  7. urkit-0.1.1/src/urkit.egg-info/PKG-INFO +0 -707
  8. {urkit-0.1.1 → urkit-0.2.0}/setup.cfg +0 -0
  9. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/__init__.py +0 -0
  10. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/__main__.py +0 -0
  11. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/cli/__init__.py +0 -0
  12. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/cli/colors.py +0 -0
  13. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/cli/connection_monitor.py +0 -0
  14. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/cli/points.py +0 -0
  15. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/cli/teach.py +0 -0
  16. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/config.py +0 -0
  17. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/connection.py +0 -0
  18. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/exceptions.py +0 -0
  19. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/geometry.py +0 -0
  20. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/gripper/__init__.py +0 -0
  21. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/gripper/base.py +0 -0
  22. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/gripper/digital.py +0 -0
  23. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/gripper/presets.py +0 -0
  24. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/gripper/robotiq.py +0 -0
  25. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/gripper/robotiq_preamble.py +0 -0
  26. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/io.py +0 -0
  27. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/motion.py +0 -0
  28. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/points.py +0 -0
  29. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/robot.py +0 -0
  30. {urkit-0.1.1 → urkit-0.2.0}/src/urkit/telemetry.py +0 -0
  31. {urkit-0.1.1 → urkit-0.2.0}/src/urkit.egg-info/SOURCES.txt +0 -0
  32. {urkit-0.1.1 → urkit-0.2.0}/src/urkit.egg-info/dependency_links.txt +0 -0
  33. {urkit-0.1.1 → urkit-0.2.0}/src/urkit.egg-info/entry_points.txt +0 -0
  34. {urkit-0.1.1 → urkit-0.2.0}/src/urkit.egg-info/requires.txt +0 -0
  35. {urkit-0.1.1 → urkit-0.2.0}/src/urkit.egg-info/top_level.txt +0 -0
  36. {urkit-0.1.1 → urkit-0.2.0}/tests/test_exceptions.py +0 -0
  37. {urkit-0.1.1 → urkit-0.2.0}/tests/test_geometry.py +0 -0
  38. {urkit-0.1.1 → urkit-0.2.0}/tests/test_gripper.py +0 -0
  39. {urkit-0.1.1 → urkit-0.2.0}/tests/test_gripper_factory.py +0 -0
  40. {urkit-0.1.1 → urkit-0.2.0}/tests/test_gripper_presets.py +0 -0
  41. {urkit-0.1.1 → urkit-0.2.0}/tests/test_points.py +0 -0
  42. {urkit-0.1.1 → urkit-0.2.0}/tests/test_robot_integration.py +0 -0
urkit-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,541 @@
1
+ Metadata-Version: 2.4
2
+ Name: urkit
3
+ Version: 0.2.0
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
+ ### Robotiq Grippers (optional)
75
+
76
+ Install the **Robotiq Gripper Control** URCap: download from [robotiq.com/support](https://robotiq.com/support), copy the `.urcap` to a USB drive, mount on the robot, and install via `☰` → `Settings` → `System` → `URCaps`.
77
+
78
+ ### Hello World
79
+
80
+ ```python
81
+ from urkit import URRobot, ROBOTIQ_HAND_E
82
+
83
+ robot = URRobot(ip="172.31.1.200", points="points.db", gripper=ROBOTIQ_HAND_E)
84
+ robot.gripper.activate()
85
+
86
+ robot.move_to("home")
87
+ robot.move_to("pick", offset=[0, 0, 0.05, 0, 0, 0])
88
+ robot.gripper.close()
89
+ robot.move_to("place")
90
+ robot.gripper.open()
91
+ ```
92
+
93
+ The typical workflow:
94
+
95
+ 1. **Teach points** — use the CLI to position the robot and save named waypoints.
96
+ 2. **Write code** — create a robot, move to points by name, apply offsets, run sequences.
97
+ 3. **Iterate** — add more points, tweak your code, repeat.
98
+
99
+ ---
100
+
101
+ ## Interactive CLI
102
+
103
+ URKit provides two CLI tools: **teach** for interactive robot control, and **points** for browsing saved waypoints.
104
+
105
+ ### Teach Mode
106
+
107
+ The interactive teach pendant for moving the robot, saving points, and checking telemetry:
108
+
109
+ ```bash
110
+ urkit teach 172.31.1.200 # with robot IP
111
+ urkit teach # reads IP from config.yaml
112
+ ```
113
+
114
+ **Flags:**
115
+
116
+ | Flag | Description |
117
+ |------|-------------|
118
+ | `ip` | Robot IP address (positional, overrides config) |
119
+ | `--gripper` | Gripper preset: `2f-85`, `2f-140`, `hand-e`, `digital`, `none` |
120
+ | `--gripper-pin` | Digital gripper output pin (default: 0) |
121
+ | `--gripper-force` | Robotiq force 0-100 |
122
+ | `--gripper-speed` | Robotiq speed 0-100 |
123
+ | `--gripper-close-on-high` | Digital polarity: `true` or `false` |
124
+ | `--points` | Path to `points.db` file (overrides config) |
125
+ | `--config` | Path to config file (default: `config.yaml` in project root or CWD) |
126
+ | `-v`, `--verbose` | Show verbose output (debug connection issues) |
127
+
128
+ ### Points Explorer
129
+
130
+ Browse saved waypoints with real-time search filtering — no robot connection needed:
131
+
132
+ ```bash
133
+ urkit points # uses default points.db
134
+ urkit points test_points.db # use specific database
135
+ ```
136
+
137
+ **Features:**
138
+ - **Type to search** — real-time substring filtering
139
+ - **Fuzzy matching** — type `pk` to find `pick_1` (>60% match)
140
+ - **Smart sorting** — exact prefix matches first, then substring, then fuzzy
141
+ - **Spatial sorting** — points ordered by proximity to "home" point (XYZ distance)
142
+ - **Theme-aware** — automatically adapts colors for light/dark terminals
143
+ - **Arrow keys** — scroll · **ESC** — quit
144
+
145
+ ### Key Map
146
+
147
+ All movement and orientation keys support **hold-to-repeat**.
148
+
149
+ <table>
150
+ <tr>
151
+ <th align="center">Movement</th>
152
+ <th align="center">Orientation</th>
153
+ <th align="center">Step Size</th>
154
+ </tr>
155
+ <tr>
156
+ <td align="center" style="width:33%">
157
+ <table>
158
+ <tr><th>Key</th><th>Action</th></tr>
159
+ <tr><td><code>W</code> / <code>S</code></td><td>+X / -X</td></tr>
160
+ <tr><td><code>A</code> / <code>D</code></td><td>+Y / -Y</td></tr>
161
+ <tr><td><code>Q</code> / <code>E</code></td><td>+Z / -Z</td></tr>
162
+ </table>
163
+ </td>
164
+ <td align="center" style="width:33%">
165
+ <table>
166
+ <tr><th>Key</th><th>Action</th></tr>
167
+ <tr><td><code>U</code> / <code>O</code></td><td>Roll - / +</td></tr>
168
+ <tr><td><code>I</code> / <code>K</code></td><td>Pitch - / +</td></tr>
169
+ <tr><td><code>J</code> / <code>L</code></td><td>Yaw - / +</td></tr>
170
+ </table>
171
+ </td>
172
+ <td align="center" style="width:34%">
173
+ <table>
174
+ <tr><th>Key</th><th>Action</th></tr>
175
+ <tr><td><code>1</code></td><td>Set linear step (mm)</td></tr>
176
+ <tr><td><code>2</code></td><td>Set angular step (deg)</td></tr>
177
+ <tr><td><code>.</code></td><td>Reset defaults (5 mm / 2°)</td></tr>
178
+ </table>
179
+ </td>
180
+ </tr>
181
+ </table>
182
+
183
+ <table>
184
+ <tr>
185
+ <th align="center">Gripper</th>
186
+ <th align="center">Points</th>
187
+ <th align="center">Controls</th>
188
+ </tr>
189
+ <tr>
190
+ <td align="center" style="width:33%">
191
+ <table>
192
+ <tr><th>Key</th><th>Action</th></tr>
193
+ <tr><td><code>X</code></td><td>Open</td></tr>
194
+ <tr><td><code>C</code></td><td>Close</td></tr>
195
+ <tr><td><code>V</code></td><td>Set position (mm)</td></tr>
196
+ </table>
197
+ </td>
198
+ <td align="center" style="width:33%">
199
+ <table>
200
+ <tr><th>Key</th><th>Action</th></tr>
201
+ <tr><td><code>B</code></td><td><strong>Save</strong> current position</td></tr>
202
+ <tr><td><code>G</code></td><td>Go to saved point</td></tr>
203
+ <tr><td><code>H</code></td><td>Delete saved point</td></tr>
204
+ <tr><td><code>P</code></td><td>Open points explorer</td></tr>
205
+ <tr><td><code>R</code></td><td>Rename saved point</td></tr>
206
+ </table>
207
+ </td>
208
+ <td align="center" style="width:34%">
209
+ <table>
210
+ <tr><th>Key</th><th>Action</th></tr>
211
+ <tr><td><code>F</code></td><td>Freedrive (OFF → ALL → XYZ+Rz)</td></tr>
212
+ <tr><td><code>M</code></td><td>Toggle frame (BASE / TOOL)</td></tr>
213
+ <tr><td><code>N</code></td><td>Go To mode (Cartesian / Joint)</td></tr>
214
+ <tr><td><code>T</code></td><td>Orient TCP down (180°)</td></tr>
215
+ <tr><td><code>Y</code></td><td>Save config to file</td></tr>
216
+ <tr><td><code>ESC</code></td><td>Exit</td></tr>
217
+ </table>
218
+ </td>
219
+ </tr>
220
+ </table>
221
+
222
+ ---
223
+
224
+ ## API Reference
225
+
226
+ ### Connecting
227
+
228
+ ```python
229
+ from urkit import URRobot, ROBOTIQ_HAND_E
230
+
231
+ robot = URRobot(ip="172.31.1.200", points="points.db", gripper=ROBOTIQ_HAND_E)
232
+ ```
233
+
234
+ With custom motion defaults:
235
+
236
+ ```python
237
+ robot = URRobot(
238
+ ip="172.31.1.200",
239
+ points="points.db",
240
+ gripper=ROBOTIQ_HAND_E,
241
+ default_vel=0.5, # m/s
242
+ default_acc=0.3, # m/s²
243
+ )
244
+ ```
245
+
246
+ From a config file:
247
+
248
+ ```python
249
+ robot = URRobot.from_config("config.yaml")
250
+ robot = URRobot.from_config("config.yaml", ip="10.0.0.50") # override IP
251
+ ```
252
+
253
+ ### Grippers
254
+
255
+ Three built-in presets:
256
+
257
+ | Preset | Description |
258
+ |--------|-------------|
259
+ | `ROBOTIQ_HAND_E` | Robotiq 2F-140-E (Hand-E series) |
260
+ | `ROBOTIQ_2F_85` | Robotiq 2F-85 |
261
+ | `ROBOTIQ_2F_140` | Robotiq 2F-140 |
262
+
263
+ ```python
264
+ robot.gripper.activate() # required before open/close (Robotiq only)
265
+ robot.gripper.is_activated() # check activation state
266
+
267
+ robot.gripper.open() # fully open (blocking by default)
268
+ robot.gripper.close() # fully closed, stops on contact
269
+ robot.gripper.open(wait=False) # non-blocking return
270
+ robot.gripper.set_position(20) # 20mm open (Robotiq only, 0 = closed)
271
+ robot.gripper.set_force(50) # grip force: 0-100
272
+ robot.gripper.set_speed(80) # movement speed: 0-100
273
+ ```
274
+
275
+ Override preset values for custom fingers:
276
+
277
+ ```python
278
+ robot = URRobot(ip="172.31.1.200", points="points.db", gripper=ROBOTIQ_HAND_E, max_mm=120)
279
+ ```
280
+
281
+ #### Digital I/O Grippers
282
+
283
+ For suction cups, solenoids, or custom actuators:
284
+
285
+ ```python
286
+ from urkit import URRobot, DigitalGripperConfig
287
+
288
+ robot = URRobot(
289
+ ip="172.31.1.200",
290
+ points="points.db",
291
+ gripper=DigitalGripperConfig(pin=3),
292
+ )
293
+
294
+ robot.gripper.open() # turn pin off (release)
295
+ robot.gripper.close() # turn pin on (grab)
296
+ ```
297
+
298
+ ### Points & Motion
299
+
300
+ The points database is optional — create a robot without one and attach later:
301
+
302
+ ```python
303
+ robot = URRobot(ip="172.31.1.200")
304
+ robot.points_db = "points.db"
305
+ ```
306
+
307
+ #### Moving to Points
308
+
309
+ ```python
310
+ robot.move_to("pick") # linear move (default)
311
+ robot.move_to("pick", linear=False) # joint move
312
+ robot.move_to("pick", vel=1.0, acc=0.5) # override speed
313
+ ```
314
+
315
+ #### Raw Poses
316
+
317
+ ```python
318
+ robot.move_to([0.5, 0, 0.3, 0, 0, 0]) # [x, y, z, rx, ry, rz]
319
+ ```
320
+
321
+ #### Offsets
322
+
323
+ ```python
324
+ robot.move_to("pick", offset=[0, 0, 0.05, 0, 0, 0]) # 5cm above pick
325
+ ```
326
+
327
+ #### Coordinate Frame
328
+
329
+ ```python
330
+ from urkit import MoveFrame
331
+
332
+ robot.move_frame = MoveFrame.TOOL # default is BASE
333
+ robot.move_to("pick", offset=[0, 0, 0.05]) # 5cm along tool Z
334
+ ```
335
+
336
+ - **BASE** (default): offset relative to robot base
337
+ - **TOOL**: offset relative to TCP orientation
338
+
339
+ #### Point Management
340
+
341
+ ```python
342
+ robot.save_point("here")
343
+ robot.point_names() # ["home", "pick", "place"]
344
+ robot.rename_point("old", "new")
345
+ robot.delete_point("old")
346
+ robot.export_points("backup.json")
347
+ robot.import_points("backup.json")
348
+ ```
349
+
350
+ #### Relative Moves
351
+
352
+ ```python
353
+ robot.move_relative([0, 0.01, 0, 0, 0, 0]) # 1cm along Y
354
+ robot.move_relative([0, 0, 0.05], frame=MoveFrame.TOOL)
355
+ ```
356
+
357
+ #### Sequences with Blending
358
+
359
+ ```python
360
+ robot.move_sequence(["a", "b", "c"])
361
+ robot.move_sequence(["a", "b", "c"], blend_radius=0.02)
362
+ ```
363
+
364
+ #### Contact Detection
365
+
366
+ ```python
367
+ robot.move_until_contact([0, 0, -0.02, 0, 0, 0]) # Ctrl+C to stop
368
+ ```
369
+
370
+ #### Velocity Control
371
+
372
+ ```python
373
+ robot.move_velocity([0, 0, -0.02, 0, 0, 0], duration=1.0)
374
+ ```
375
+
376
+ #### Freedrive Mode
377
+
378
+ ```python
379
+ from urkit import FreedriveMode
380
+
381
+ robot.enable_freedrive() # all 6 axes free
382
+ robot.enable_freedrive(FreedriveMode.XYZ) # linear axes + Rz rotation
383
+ robot.enable_freedrive(FreedriveMode.ROTATION) # rotation only
384
+ robot.disable_freedrive() # disable before sending motion commands
385
+ robot.is_freedrive_active # check state
386
+ ```
387
+
388
+ #### Speed Control
389
+
390
+ ```python
391
+ robot.speed_stop() # emergency stop
392
+ robot.set_speed_slider(0.5) # 50% hardware velocity cap
393
+ ```
394
+
395
+ 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.
396
+
397
+ #### Inverse Kinematics
398
+
399
+ ```python
400
+ joints = robot.inverse_kinematics([0.5, 0, 0.3, 0, 0, 0])
401
+ ```
402
+
403
+ ### Telemetry
404
+
405
+ ```python
406
+ pose = robot.get_tcp_pose() # [x, y, z, rx, ry, rz]
407
+ joints = robot.get_joint_positions() # [j0..j5]
408
+ force = robot.get_tcp_force() # [fx, fy, fz, mx, my, mz]
409
+ mode = robot.get_robot_mode() # "REMOTE_CONTROL", "SERVOING", etc.
410
+ scaling = robot.get_speed_scaling() # 0.0-1.0
411
+ payload = robot.get_payload() # kg
412
+ robot.is_protective_stopped() # bool
413
+ robot.is_emergency_stopped() # bool
414
+ robot.current_point() # {"pose": [...], "joints": [...]}
415
+ ```
416
+
417
+ ### Digital I/O
418
+
419
+ ```python
420
+ robot.set_digital_output(0, True)
421
+ robot.set_digital_outputs({0: True, 1: False, 8: True})
422
+ robot.set_digital_outputs(False) # clear all
423
+
424
+ robot.get_digital_input(0)
425
+ robot.get_analog_input(0)
426
+ robot.get_tool_input(0)
427
+
428
+ robot.wait_for_input(0, True, timeout=10.0) # block until pin 0 goes high
429
+ ```
430
+
431
+ ---
432
+
433
+ ## Configuration
434
+
435
+ URKit uses a YAML config file (`config.yaml`) to persist settings between sessions.
436
+
437
+ ### Location
438
+
439
+ URKit searches for `config.yaml` in this order:
440
+ 1. Explicit path via `--config` flag or `load_config("path")`
441
+ 2. Project root (where `src/urkit` lives)
442
+ 3. Current working directory
443
+
444
+ ### Keys
445
+
446
+ | Key | Description | Example |
447
+ |-----|-------------|---------|
448
+ | `robot_ip` | Robot IP address | `192.168.1.100` |
449
+ | `points_path` | Path to SQLite points database | `points.db` |
450
+ | `gripper` | Gripper preset name | `hand-e`, `2f-85`, `2f-140`, `digital` |
451
+ | `default_vel` | Default linear velocity (m/s) | `0.5` |
452
+ | `default_acc` | Default linear acceleration (m/s²) | `0.3` |
453
+
454
+ ### Gripper Config
455
+
456
+ ```yaml
457
+ gripper: digital
458
+ gripper_config:
459
+ pin: 3
460
+ close_on_high: true
461
+ ```
462
+
463
+ ```yaml
464
+ gripper: hand-e
465
+ gripper_config:
466
+ force: 50
467
+ speed: 80
468
+ ```
469
+
470
+ ### CLI Override Precedence
471
+
472
+ 1. **CLI flags** — `urkit teach 172.31.1.200 --gripper none`
473
+ 2. **Config file** — values from `config.yaml`
474
+ 3. **Built-in defaults** — `points.db`, no gripper, 0.5 m/s velocity
475
+
476
+ ### Saving Config
477
+
478
+ 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.
479
+
480
+ ```bash
481
+ urkit teach 172.31.1.200 --gripper hand-e # test, then press Y
482
+ urkit teach # next time: reads from config
483
+ ```
484
+
485
+ Multiple workcells:
486
+
487
+ ```bash
488
+ urkit teach --config station_a.yaml # press Y to save
489
+ urkit teach --config station_b.yaml # separate config
490
+ ```
491
+
492
+ ### Programmatic
493
+
494
+ ```python
495
+ from urkit import load_config, resolve_config
496
+
497
+ config = load_config() # auto-resolve
498
+ config = load_config("/path/to/my.yaml") # explicit path
499
+ path = resolve_config() # returns Path or None
500
+ robot = URRobot.from_config({"robot_ip": "172.31.1.200", "gripper": "2f-85"})
501
+ ```
502
+
503
+ ---
504
+
505
+ ## Advanced
506
+
507
+ ### Raw RTDE Access
508
+
509
+ URKit doesn't try to wrap everything. Access the raw `ur_rtde` interfaces for advanced features:
510
+
511
+ ```python
512
+ robot.rtde_control.moveUntilContact([0, 0, -0.02, 0, 0, 0])
513
+ robot.rtde_control.forceMode(...)
514
+ robot.rtde_control.servoJ(...)
515
+ robot.rtde_receive.getActualCurrent()
516
+ ```
517
+
518
+ Full `ur_rtde` documentation: <https://sdurobotics.gitlab.io/ur_rtde/>
519
+
520
+ ### Connection Lifecycle
521
+
522
+ ```python
523
+ robot.connection_lost # bool: check if RTDE dropped
524
+ robot.reconnect_rtde() # reconnect after a drop
525
+ robot.disconnect() # clean shutdown
526
+ ```
527
+
528
+ ### Error Handling
529
+
530
+ ```python
531
+ from urkit import URKitError, RobotNotInRemoteModeError, RtdeRegisterConflictError
532
+
533
+ try:
534
+ robot = URRobot(ip="172.31.1.200", points="points.db")
535
+ except RobotNotInRemoteModeError:
536
+ print("Enable remote control on the teach pendant!")
537
+ except RtdeRegisterConflictError:
538
+ print("Disable EtherNet/IP, PROFINET, or MODBUS!")
539
+ except URKitError as e:
540
+ print(f"Error: {e}")
541
+ ```