urkit 0.1.0__tar.gz → 0.1.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.
- {urkit-0.1.0 → urkit-0.1.1}/PKG-INFO +108 -13
- {urkit-0.1.0 → urkit-0.1.1}/README.md +106 -12
- {urkit-0.1.0 → urkit-0.1.1}/pyproject.toml +5 -1
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/__init__.py +3 -1
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/__main__.py +8 -2
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/cli/teach.py +64 -46
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/connection.py +10 -3
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/gripper/base.py +19 -9
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/gripper/digital.py +0 -10
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/gripper/robotiq.py +21 -14
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/motion.py +2 -2
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/robot.py +2 -7
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit.egg-info/PKG-INFO +108 -13
- {urkit-0.1.0 → urkit-0.1.1}/tests/test_gripper.py +43 -5
- {urkit-0.1.0 → urkit-0.1.1}/tests/test_gripper_factory.py +1 -5
- {urkit-0.1.0 → urkit-0.1.1}/setup.cfg +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/cli/__init__.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/cli/colors.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/cli/connection_monitor.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/cli/points.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/config.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/exceptions.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/geometry.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/gripper/__init__.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/gripper/presets.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/gripper/robotiq_preamble.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/io.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/points.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit/telemetry.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit.egg-info/SOURCES.txt +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit.egg-info/dependency_links.txt +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit.egg-info/entry_points.txt +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit.egg-info/requires.txt +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/src/urkit.egg-info/top_level.txt +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/tests/test_exceptions.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/tests/test_geometry.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/tests/test_gripper_presets.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/tests/test_points.py +0 -0
- {urkit-0.1.0 → urkit-0.1.1}/tests/test_robot_integration.py +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: urkit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Universal Robots e-Series control toolkit built on ur_rtde
|
|
5
5
|
Author: URKit Contributors
|
|
6
6
|
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/rodolfo-verde/urkit
|
|
7
8
|
Keywords: universal-robots,ur3e,ur5e,ur7e,ur10e,ur12e,ur16e,ur8,ur15,ur18,ur20,ur30,rtde,robotics,industrial-robot
|
|
8
9
|
Classifier: Development Status :: 3 - Alpha
|
|
9
10
|
Classifier: Intended Audience :: Developers
|
|
@@ -25,6 +26,8 @@ Requires-Dist: rich>=13.0.0
|
|
|
25
26
|
|
|
26
27
|
# URKit
|
|
27
28
|
|
|
29
|
+
[](https://pypi.org/project/urkit/)
|
|
30
|
+
|
|
28
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.
|
|
29
32
|
|
|
30
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. It doesn't try to replace `ur_rtde`; it sits on top of it so you can use both in tandem.
|
|
@@ -103,17 +106,20 @@ urkit teach # reads IP from config.yaml
|
|
|
103
106
|
| Flag | Description |
|
|
104
107
|
|------|-------------|
|
|
105
108
|
| `ip` | Robot IP address (positional, overrides config) |
|
|
106
|
-
| `--gripper` | Gripper preset: `2f-85`, `2f-140`, `hand-e`, `digital` |
|
|
109
|
+
| `--gripper` | Gripper preset: `2f-85`, `2f-140`, `hand-e`, `digital`, `none` |
|
|
107
110
|
| `--gripper-pin` | Digital gripper output pin (default: 0) |
|
|
108
111
|
| `--gripper-force` | Robotiq force 0-100 |
|
|
109
112
|
| `--gripper-speed` | Robotiq speed 0-100 |
|
|
110
113
|
| `--gripper-close-on-high` | Digital polarity: `true` or `false` |
|
|
111
114
|
| `--points` | Path to `points.db` file (overrides config) |
|
|
115
|
+
| `--config` | Path to config file (default: `config.yaml` in project root or CWD) |
|
|
112
116
|
| `-v`, `--verbose` | Show verbose output (debug connection issues) |
|
|
113
117
|
|
|
114
118
|
```bash
|
|
115
119
|
urkit teach 172.31.1.200 --gripper hand-e --points /path/to/points.db
|
|
116
120
|
urkit teach --gripper digital --gripper-pin 3
|
|
121
|
+
urkit teach --gripper none # no gripper (overrides config)
|
|
122
|
+
urkit teach --config /path/to/my.yaml # load custom config
|
|
117
123
|
urkit teach -v # verbose mode
|
|
118
124
|
```
|
|
119
125
|
|
|
@@ -210,10 +216,11 @@ All movement and orientation keys support **hold-to-repeat** — hold a key down
|
|
|
210
216
|
<td align="center" style="width:50%">
|
|
211
217
|
<table>
|
|
212
218
|
<tr><th>Key</th><th>Action</th></tr>
|
|
213
|
-
<tr><td><code>F</code></td><td>Freedrive (OFF → ALL → XYZ)</td></tr>
|
|
219
|
+
<tr><td><code>F</code></td><td>Freedrive (OFF → ALL → XYZ+Rz)</td></tr>
|
|
214
220
|
<tr><td><code>M</code></td><td>Toggle frame (BASE / TOOL)</td></tr>
|
|
215
221
|
<tr><td><code>N</code></td><td>Go To mode (Cartesian / Joint)</td></tr>
|
|
216
222
|
<tr><td><code>T</code></td><td>Orient TCP down (180°)</td></tr>
|
|
223
|
+
<tr><td><code>Y</code></td><td>Save config to file</td></tr>
|
|
217
224
|
<tr><td><code>ESC</code></td><td>Exit</td></tr>
|
|
218
225
|
</table>
|
|
219
226
|
</td>
|
|
@@ -249,22 +256,110 @@ robot = URRobot(
|
|
|
249
256
|
|
|
250
257
|
### From Config
|
|
251
258
|
|
|
252
|
-
|
|
259
|
+
```python
|
|
260
|
+
robot = URRobot.from_config("config.yaml")
|
|
261
|
+
robot = URRobot.from_config("config.yaml", ip="10.0.0.50") # override IP
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
See the [Configuration](#configuration) section for full details on config location, keys, and saving.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Configuration
|
|
269
|
+
|
|
270
|
+
URKit uses a YAML config file (`config.yaml`) to persist settings between sessions. The CLI reads it automatically, and `URRobot.from_config()` loads it programmatically.
|
|
271
|
+
|
|
272
|
+
### Config File Location
|
|
273
|
+
|
|
274
|
+
URKit searches for `config.yaml` in this order:
|
|
275
|
+
1. Explicit path via `--config` flag or `load_config("path")`
|
|
276
|
+
2. Project root (where `src/urkit` lives)
|
|
277
|
+
3. Current working directory
|
|
278
|
+
|
|
279
|
+
If no config file exists, URKit uses built-in defaults and operates fine — the config is optional.
|
|
280
|
+
|
|
281
|
+
### Config Keys
|
|
282
|
+
|
|
283
|
+
| Key | Description | Example |
|
|
284
|
+
|-----|-------------|---------|
|
|
285
|
+
| `robot_ip` | Robot IP address | `192.168.1.100` |
|
|
286
|
+
| `points_path` | Path to SQLite points database | `points.db` |
|
|
287
|
+
| `gripper` | Gripper preset name | `hand-e`, `2f-85`, `2f-140`, `digital` |
|
|
288
|
+
| `default_vel` | Default linear velocity (m/s) | `0.5` |
|
|
289
|
+
| `default_acc` | Default linear acceleration (m/s²) | `0.3` |
|
|
290
|
+
| `rtde_frequency` | RTDE communication frequency (Hz) | `125` |
|
|
291
|
+
| `rtde_frequency` | RTDE communication frequency (Hz) | `125` |
|
|
292
|
+
| `rtde_frequency` | RTDE communication frequency (Hz) | `125` |
|
|
293
|
+
| `rtde_frequency` | RTDE communication frequency (Hz) | `125` |
|
|
294
|
+
| `rtde_frequency` | RTDE communication frequency (Hz) | `125` |
|
|
295
|
+
|
|
296
|
+
### Gripper Config Section
|
|
297
|
+
|
|
298
|
+
For digital grippers, specify pin and polarity:
|
|
299
|
+
|
|
300
|
+
```yaml
|
|
301
|
+
gripper: digital
|
|
302
|
+
gripper_config:
|
|
303
|
+
pin: 3
|
|
304
|
+
close_on_high: true
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
For Robotiq grippers, override preset values:
|
|
253
308
|
|
|
254
309
|
```yaml
|
|
255
|
-
robot_ip: 172.31.1.200
|
|
256
|
-
points_path: points.db
|
|
257
310
|
gripper: hand-e
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
311
|
+
gripper_config:
|
|
312
|
+
force: 50
|
|
313
|
+
speed: 80
|
|
261
314
|
```
|
|
262
315
|
|
|
263
|
-
|
|
316
|
+
### CLI Override Precedence
|
|
317
|
+
|
|
318
|
+
Settings are resolved in this order (highest priority first):
|
|
319
|
+
|
|
320
|
+
1. **CLI flags** — `urkit teach 172.31.1.200 --gripper none`
|
|
321
|
+
2. **Config file** — values from `config.yaml`
|
|
322
|
+
3. **Built-in defaults** — `points.db` for points, no gripper, `0.5` m/s velocity
|
|
323
|
+
|
|
324
|
+
Use `--gripper none` to explicitly disable a gripper that's set in your config file.
|
|
325
|
+
|
|
326
|
+
### Saving Config
|
|
327
|
+
|
|
328
|
+
The CLI **never** modifies your config file automatically. Inside the teach pendant, press **Y** to save your current session's settings (IP, gripper, points path) to the config file. This way you only save settings you've actually tested and verified work.
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# First connection — test everything, then press Y inside the pendant
|
|
332
|
+
urkit teach 172.31.1.200 --gripper hand-e
|
|
333
|
+
|
|
334
|
+
# After pressing Y, config.yaml is saved. Next time:
|
|
335
|
+
urkit teach # reads IP + gripper from config
|
|
336
|
+
|
|
337
|
+
# Custom config file
|
|
338
|
+
urkit teach --config station_a.yaml # load from custom path
|
|
339
|
+
# press Y inside → saves back to station_a.yaml
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
This lets you maintain separate configs per workcell:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
urkit teach --config station_a.yaml # press Y to save
|
|
346
|
+
urkit teach --config station_b.yaml # press Y to save
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Programmatic Config
|
|
264
350
|
|
|
265
351
|
```python
|
|
266
|
-
|
|
267
|
-
|
|
352
|
+
from urkit import load_config, resolve_config
|
|
353
|
+
|
|
354
|
+
# Load with auto-resolution
|
|
355
|
+
config = load_config() # searches for config.yaml
|
|
356
|
+
config = load_config("/path/to/my.yaml") # explicit path
|
|
357
|
+
|
|
358
|
+
# Check if config exists
|
|
359
|
+
path = resolve_config() # returns Path or None
|
|
360
|
+
|
|
361
|
+
# Create robot from config dict
|
|
362
|
+
robot = URRobot.from_config({"robot_ip": "172.31.1.200", "gripper": "2f-85"})
|
|
268
363
|
```
|
|
269
364
|
|
|
270
365
|
---
|
|
@@ -478,7 +573,7 @@ from urkit import FreedriveMode
|
|
|
478
573
|
|
|
479
574
|
# Freedrive lets you manually push the robot: motion commands won't work while active
|
|
480
575
|
robot.enable_freedrive() # all 6 axes free
|
|
481
|
-
robot.enable_freedrive(FreedriveMode.XYZ) # linear axes
|
|
576
|
+
robot.enable_freedrive(FreedriveMode.XYZ) # linear axes + Rz rotation
|
|
482
577
|
robot.enable_freedrive(FreedriveMode.ROTATION) # rotation only
|
|
483
578
|
robot.disable_freedrive() # always disable before sending motion commands
|
|
484
579
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# URKit
|
|
2
2
|
|
|
3
|
+
[](https://pypi.org/project/urkit/)
|
|
4
|
+
|
|
3
5
|
**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.
|
|
4
6
|
|
|
5
7
|
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. It doesn't try to replace `ur_rtde`; it sits on top of it so you can use both in tandem.
|
|
@@ -78,17 +80,20 @@ urkit teach # reads IP from config.yaml
|
|
|
78
80
|
| Flag | Description |
|
|
79
81
|
|------|-------------|
|
|
80
82
|
| `ip` | Robot IP address (positional, overrides config) |
|
|
81
|
-
| `--gripper` | Gripper preset: `2f-85`, `2f-140`, `hand-e`, `digital` |
|
|
83
|
+
| `--gripper` | Gripper preset: `2f-85`, `2f-140`, `hand-e`, `digital`, `none` |
|
|
82
84
|
| `--gripper-pin` | Digital gripper output pin (default: 0) |
|
|
83
85
|
| `--gripper-force` | Robotiq force 0-100 |
|
|
84
86
|
| `--gripper-speed` | Robotiq speed 0-100 |
|
|
85
87
|
| `--gripper-close-on-high` | Digital polarity: `true` or `false` |
|
|
86
88
|
| `--points` | Path to `points.db` file (overrides config) |
|
|
89
|
+
| `--config` | Path to config file (default: `config.yaml` in project root or CWD) |
|
|
87
90
|
| `-v`, `--verbose` | Show verbose output (debug connection issues) |
|
|
88
91
|
|
|
89
92
|
```bash
|
|
90
93
|
urkit teach 172.31.1.200 --gripper hand-e --points /path/to/points.db
|
|
91
94
|
urkit teach --gripper digital --gripper-pin 3
|
|
95
|
+
urkit teach --gripper none # no gripper (overrides config)
|
|
96
|
+
urkit teach --config /path/to/my.yaml # load custom config
|
|
92
97
|
urkit teach -v # verbose mode
|
|
93
98
|
```
|
|
94
99
|
|
|
@@ -185,10 +190,11 @@ All movement and orientation keys support **hold-to-repeat** — hold a key down
|
|
|
185
190
|
<td align="center" style="width:50%">
|
|
186
191
|
<table>
|
|
187
192
|
<tr><th>Key</th><th>Action</th></tr>
|
|
188
|
-
<tr><td><code>F</code></td><td>Freedrive (OFF → ALL → XYZ)</td></tr>
|
|
193
|
+
<tr><td><code>F</code></td><td>Freedrive (OFF → ALL → XYZ+Rz)</td></tr>
|
|
189
194
|
<tr><td><code>M</code></td><td>Toggle frame (BASE / TOOL)</td></tr>
|
|
190
195
|
<tr><td><code>N</code></td><td>Go To mode (Cartesian / Joint)</td></tr>
|
|
191
196
|
<tr><td><code>T</code></td><td>Orient TCP down (180°)</td></tr>
|
|
197
|
+
<tr><td><code>Y</code></td><td>Save config to file</td></tr>
|
|
192
198
|
<tr><td><code>ESC</code></td><td>Exit</td></tr>
|
|
193
199
|
</table>
|
|
194
200
|
</td>
|
|
@@ -224,22 +230,110 @@ robot = URRobot(
|
|
|
224
230
|
|
|
225
231
|
### From Config
|
|
226
232
|
|
|
227
|
-
|
|
233
|
+
```python
|
|
234
|
+
robot = URRobot.from_config("config.yaml")
|
|
235
|
+
robot = URRobot.from_config("config.yaml", ip="10.0.0.50") # override IP
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
See the [Configuration](#configuration) section for full details on config location, keys, and saving.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Configuration
|
|
243
|
+
|
|
244
|
+
URKit uses a YAML config file (`config.yaml`) to persist settings between sessions. The CLI reads it automatically, and `URRobot.from_config()` loads it programmatically.
|
|
245
|
+
|
|
246
|
+
### Config File Location
|
|
247
|
+
|
|
248
|
+
URKit searches for `config.yaml` in this order:
|
|
249
|
+
1. Explicit path via `--config` flag or `load_config("path")`
|
|
250
|
+
2. Project root (where `src/urkit` lives)
|
|
251
|
+
3. Current working directory
|
|
252
|
+
|
|
253
|
+
If no config file exists, URKit uses built-in defaults and operates fine — the config is optional.
|
|
254
|
+
|
|
255
|
+
### Config Keys
|
|
256
|
+
|
|
257
|
+
| Key | Description | Example |
|
|
258
|
+
|-----|-------------|---------|
|
|
259
|
+
| `robot_ip` | Robot IP address | `192.168.1.100` |
|
|
260
|
+
| `points_path` | Path to SQLite points database | `points.db` |
|
|
261
|
+
| `gripper` | Gripper preset name | `hand-e`, `2f-85`, `2f-140`, `digital` |
|
|
262
|
+
| `default_vel` | Default linear velocity (m/s) | `0.5` |
|
|
263
|
+
| `default_acc` | Default linear acceleration (m/s²) | `0.3` |
|
|
264
|
+
| `rtde_frequency` | RTDE communication frequency (Hz) | `125` |
|
|
265
|
+
| `rtde_frequency` | RTDE communication frequency (Hz) | `125` |
|
|
266
|
+
| `rtde_frequency` | RTDE communication frequency (Hz) | `125` |
|
|
267
|
+
| `rtde_frequency` | RTDE communication frequency (Hz) | `125` |
|
|
268
|
+
| `rtde_frequency` | RTDE communication frequency (Hz) | `125` |
|
|
269
|
+
|
|
270
|
+
### Gripper Config Section
|
|
271
|
+
|
|
272
|
+
For digital grippers, specify pin and polarity:
|
|
273
|
+
|
|
274
|
+
```yaml
|
|
275
|
+
gripper: digital
|
|
276
|
+
gripper_config:
|
|
277
|
+
pin: 3
|
|
278
|
+
close_on_high: true
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
For Robotiq grippers, override preset values:
|
|
228
282
|
|
|
229
283
|
```yaml
|
|
230
|
-
robot_ip: 172.31.1.200
|
|
231
|
-
points_path: points.db
|
|
232
284
|
gripper: hand-e
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
285
|
+
gripper_config:
|
|
286
|
+
force: 50
|
|
287
|
+
speed: 80
|
|
236
288
|
```
|
|
237
289
|
|
|
238
|
-
|
|
290
|
+
### CLI Override Precedence
|
|
291
|
+
|
|
292
|
+
Settings are resolved in this order (highest priority first):
|
|
293
|
+
|
|
294
|
+
1. **CLI flags** — `urkit teach 172.31.1.200 --gripper none`
|
|
295
|
+
2. **Config file** — values from `config.yaml`
|
|
296
|
+
3. **Built-in defaults** — `points.db` for points, no gripper, `0.5` m/s velocity
|
|
297
|
+
|
|
298
|
+
Use `--gripper none` to explicitly disable a gripper that's set in your config file.
|
|
299
|
+
|
|
300
|
+
### Saving Config
|
|
301
|
+
|
|
302
|
+
The CLI **never** modifies your config file automatically. Inside the teach pendant, press **Y** to save your current session's settings (IP, gripper, points path) to the config file. This way you only save settings you've actually tested and verified work.
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
# First connection — test everything, then press Y inside the pendant
|
|
306
|
+
urkit teach 172.31.1.200 --gripper hand-e
|
|
307
|
+
|
|
308
|
+
# After pressing Y, config.yaml is saved. Next time:
|
|
309
|
+
urkit teach # reads IP + gripper from config
|
|
310
|
+
|
|
311
|
+
# Custom config file
|
|
312
|
+
urkit teach --config station_a.yaml # load from custom path
|
|
313
|
+
# press Y inside → saves back to station_a.yaml
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
This lets you maintain separate configs per workcell:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
urkit teach --config station_a.yaml # press Y to save
|
|
320
|
+
urkit teach --config station_b.yaml # press Y to save
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Programmatic Config
|
|
239
324
|
|
|
240
325
|
```python
|
|
241
|
-
|
|
242
|
-
|
|
326
|
+
from urkit import load_config, resolve_config
|
|
327
|
+
|
|
328
|
+
# Load with auto-resolution
|
|
329
|
+
config = load_config() # searches for config.yaml
|
|
330
|
+
config = load_config("/path/to/my.yaml") # explicit path
|
|
331
|
+
|
|
332
|
+
# Check if config exists
|
|
333
|
+
path = resolve_config() # returns Path or None
|
|
334
|
+
|
|
335
|
+
# Create robot from config dict
|
|
336
|
+
robot = URRobot.from_config({"robot_ip": "172.31.1.200", "gripper": "2f-85"})
|
|
243
337
|
```
|
|
244
338
|
|
|
245
339
|
---
|
|
@@ -453,7 +547,7 @@ from urkit import FreedriveMode
|
|
|
453
547
|
|
|
454
548
|
# Freedrive lets you manually push the robot: motion commands won't work while active
|
|
455
549
|
robot.enable_freedrive() # all 6 axes free
|
|
456
|
-
robot.enable_freedrive(FreedriveMode.XYZ) # linear axes
|
|
550
|
+
robot.enable_freedrive(FreedriveMode.XYZ) # linear axes + Rz rotation
|
|
457
551
|
robot.enable_freedrive(FreedriveMode.ROTATION) # rotation only
|
|
458
552
|
robot.disable_freedrive() # always disable before sending motion commands
|
|
459
553
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "urkit"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.1"
|
|
8
8
|
description = "Universal Robots e-Series control toolkit built on ur_rtde"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -12,6 +12,7 @@ requires-python = ">=3.8"
|
|
|
12
12
|
authors = [
|
|
13
13
|
{name = "URKit Contributors"},
|
|
14
14
|
]
|
|
15
|
+
|
|
15
16
|
keywords = [
|
|
16
17
|
"universal-robots",
|
|
17
18
|
"ur3e",
|
|
@@ -49,6 +50,9 @@ dependencies = [
|
|
|
49
50
|
"rich>=13.0.0",
|
|
50
51
|
]
|
|
51
52
|
|
|
53
|
+
[project.urls]
|
|
54
|
+
Repository = "https://github.com/rodolfo-verde/urkit"
|
|
55
|
+
|
|
52
56
|
[project.scripts]
|
|
53
57
|
urkit = "urkit.__main__:main_entry"
|
|
54
58
|
|
|
@@ -24,6 +24,8 @@ Quick start::
|
|
|
24
24
|
|
|
25
25
|
from __future__ import annotations
|
|
26
26
|
|
|
27
|
+
from importlib.metadata import version as _metadata_version
|
|
28
|
+
|
|
27
29
|
from urkit.config import load_config, resolve_config
|
|
28
30
|
from urkit.exceptions import (
|
|
29
31
|
URKitError,
|
|
@@ -57,7 +59,7 @@ from urkit.gripper.presets import (
|
|
|
57
59
|
from urkit.motion import FreedriveMode
|
|
58
60
|
from urkit.robot import URRobot
|
|
59
61
|
|
|
60
|
-
__version__ = "
|
|
62
|
+
__version__ = _metadata_version("urkit")
|
|
61
63
|
|
|
62
64
|
__all__ = [
|
|
63
65
|
# Version
|
|
@@ -41,8 +41,8 @@ def main() -> None:
|
|
|
41
41
|
"--gripper",
|
|
42
42
|
type=str,
|
|
43
43
|
default=None,
|
|
44
|
-
choices=["2f-85", "2f-140", "hand-e", "digital"],
|
|
45
|
-
help="Gripper preset (overrides config file)",
|
|
44
|
+
choices=["2f-85", "2f-140", "hand-e", "digital", "none"],
|
|
45
|
+
help="Gripper preset (overrides config file). Use 'none' to disable gripper",
|
|
46
46
|
)
|
|
47
47
|
teach_parser.add_argument(
|
|
48
48
|
"--gripper-pin",
|
|
@@ -75,6 +75,12 @@ def main() -> None:
|
|
|
75
75
|
default=None,
|
|
76
76
|
help="Path to points.db file (overrides config file)",
|
|
77
77
|
)
|
|
78
|
+
teach_parser.add_argument(
|
|
79
|
+
"--config",
|
|
80
|
+
type=str,
|
|
81
|
+
default=None,
|
|
82
|
+
help="Path to config file (default: config.yaml in project root or CWD)",
|
|
83
|
+
)
|
|
78
84
|
teach_parser.add_argument(
|
|
79
85
|
"-v", "--verbose",
|
|
80
86
|
action="store_true",
|
|
@@ -101,10 +101,15 @@ def _compute_vel_acc(
|
|
|
101
101
|
acc = min(vel * 3.0, _MAX_ACC)
|
|
102
102
|
return vel, acc
|
|
103
103
|
|
|
104
|
-
def _save_config(config: dict) -> None:
|
|
105
|
-
"""Write configuration to
|
|
104
|
+
def _save_config(config: dict, path: str | Path | None = None) -> None:
|
|
105
|
+
"""Write configuration to config.yaml.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
config: Configuration dict to save.
|
|
109
|
+
path: Explicit path. Falls back to resolved default config path.
|
|
110
|
+
"""
|
|
106
111
|
try:
|
|
107
|
-
config_path = _resolve_default_config_path()
|
|
112
|
+
config_path = Path(path) if path is not None else _resolve_default_config_path()
|
|
108
113
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
109
114
|
with open(config_path, "w") as f:
|
|
110
115
|
yaml.dump(config, f, default_flow_style=False)
|
|
@@ -186,13 +191,14 @@ def _draw_screen(
|
|
|
186
191
|
tcp_force = [0, 0, 0, 0, 0, 0]
|
|
187
192
|
|
|
188
193
|
gripper_state = "None"
|
|
189
|
-
if robot.gripper
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
if robot.gripper:
|
|
195
|
+
pos_mm = robot.gripper.get_position_mm()
|
|
196
|
+
max_mm = robot.gripper.max_travel_mm()
|
|
197
|
+
if pos_mm is not None and max_mm is not None and max_mm > 0:
|
|
198
|
+
pct = int((max_mm - pos_mm) / max_mm * 100)
|
|
199
|
+
gripper_state = f"{green('Connected')} {pos_mm:.1f}mm ({pct}%)"
|
|
200
|
+
else:
|
|
201
|
+
gripper_state = green("Connected")
|
|
196
202
|
|
|
197
203
|
lines: list[str] = []
|
|
198
204
|
|
|
@@ -212,9 +218,12 @@ def _draw_screen(
|
|
|
212
218
|
lines.append(f" {blue('Go To:'.ljust(lw))} {green(goto_label)} {dim('[N: toggle Cartesian/Joint]')}")
|
|
213
219
|
lines.append(f" {blue('Gripper:'.ljust(lw))} {gripper_state}")
|
|
214
220
|
if state["freedrive"]:
|
|
215
|
-
|
|
221
|
+
mode_label = state["freedrive_mode"].name
|
|
222
|
+
if mode_label == "XYZ":
|
|
223
|
+
mode_label = "XYZ+Rz"
|
|
224
|
+
lines.append(f" {blue('Freedrive:'.ljust(lw))} {green('ON')} ({mode_label})")
|
|
216
225
|
else:
|
|
217
|
-
lines.append(f" {blue('Freedrive:'.ljust(lw))} {red('OFF')} {dim('[F: cycle ALL/XYZ]')}")
|
|
226
|
+
lines.append(f" {blue('Freedrive:'.ljust(lw))} {red('OFF')} {dim('[F: cycle ALL/XYZ+Rz]')}")
|
|
218
227
|
slider_pct = int(state["speed_slider"] * 100)
|
|
219
228
|
slider_color = green if state["speed_slider"] >= 0.5 else yellow if state["speed_slider"] >= 0.2 else red
|
|
220
229
|
lines.append(f" {blue('Speed:'.ljust(lw))} {slider_color(f'{slider_pct}%')} {dim('[0: set]')}")
|
|
@@ -245,16 +254,18 @@ def _draw_screen(
|
|
|
245
254
|
# Control reference
|
|
246
255
|
lines.append(f" {yellow('MOVE:')} {yellow('W/S')}: ±X {yellow('A/D')}: ±Y {yellow('Q/E')}: ±Z")
|
|
247
256
|
lines.append(f" {yellow('ORIENT:')} {yellow('U/O')}: ±Roll {yellow('I/K')}: ±Pitch {yellow('J/L')}: ±Yaw")
|
|
248
|
-
lines.append(f" {yellow('STEP:')} {yellow('1')}:
|
|
249
|
-
lines.append(f" {yellow('GRIPPER:')} {yellow('X')}: Open {yellow('C')}: Close {yellow('V')}:
|
|
250
|
-
lines.append(f" {yellow('POINTS:')} {yellow('B')}: Save {yellow('G')}: Go To {yellow('H')}: Delete {yellow('R')}: Rename")
|
|
251
|
-
lines.append(f" {yellow('OTHER:')} {yellow('F')}: Freedrive {yellow('M')}: Frame {yellow('N')}:
|
|
252
|
-
lines.append(f" {yellow('
|
|
257
|
+
lines.append(f" {yellow('STEP:')} {yellow('1')}: Linear (mm) {yellow('2')}: Angular (°) {yellow('.')}: Reset")
|
|
258
|
+
lines.append(f" {yellow('GRIPPER:')} {yellow('X')}: Open {yellow('C')}: Close {yellow('V')}: Position")
|
|
259
|
+
lines.append(f" {yellow('POINTS:')} {yellow('B')}: Save {yellow('G')}: Go To {yellow('H')}: Delete {yellow('R')}: Rename {yellow('P')}: Explorer")
|
|
260
|
+
lines.append(f" {yellow('OTHER:')} {yellow('F')}: Freedrive {yellow('M')}: Frame {yellow('N')}: GoTo Mode {yellow('T')}: TCP Down")
|
|
261
|
+
lines.append(f" {yellow(' ')} {yellow('0')}: Speed {yellow('Y')}: Save Config")
|
|
262
|
+
lines.append(f" {yellow('EXIT:')} {yellow('ESC')}")
|
|
253
263
|
lines.append(dim("=" * width))
|
|
254
264
|
|
|
255
265
|
# Clear and redraw
|
|
256
|
-
|
|
257
|
-
|
|
266
|
+
sys.stdout.write("\033[2J\033[1;1H")
|
|
267
|
+
sys.stdout.write("\n".join(lines) + "\n")
|
|
268
|
+
sys.stdout.flush()
|
|
258
269
|
|
|
259
270
|
|
|
260
271
|
def _draw_help() -> None:
|
|
@@ -288,14 +299,16 @@ def _draw_help() -> None:
|
|
|
288
299
|
lines.append(" G → Go to saved point (ask cartesian/joint)")
|
|
289
300
|
lines.append(" H → Delete saved point")
|
|
290
301
|
lines.append(" R → Rename saved point")
|
|
302
|
+
lines.append(" P → Open points explorer")
|
|
291
303
|
lines.append("")
|
|
292
304
|
lines.append(" OTHER:")
|
|
293
|
-
lines.append(" F → Cycle freedrive: OFF → ALL → XYZ → OFF")
|
|
305
|
+
lines.append(" F → Cycle freedrive: OFF → ALL → XYZ+Rz → OFF")
|
|
294
306
|
lines.append(" M → Toggle move frame: BASE / TOOL")
|
|
295
307
|
lines.append(" T → Orient TCP downward (roll=180°)")
|
|
296
308
|
lines.append(" 0 → Set speed slider (0-100%)")
|
|
309
|
+
lines.append(" Y → Save config (IP, gripper, points path)")
|
|
297
310
|
lines.append("")
|
|
298
|
-
lines.append("
|
|
311
|
+
lines.append(" Exit → ESC")
|
|
299
312
|
lines.append("=" * width)
|
|
300
313
|
print("\n".join(lines))
|
|
301
314
|
|
|
@@ -710,6 +723,10 @@ class _ScreenLogHandler(logging.Handler):
|
|
|
710
723
|
|
|
711
724
|
def _teach_pendant(
|
|
712
725
|
robot: URRobot,
|
|
726
|
+
*,
|
|
727
|
+
config_path: str | Path | None = None,
|
|
728
|
+
current_gripper_name: str | None = None,
|
|
729
|
+
current_points_path: str | None = None,
|
|
713
730
|
) -> None:
|
|
714
731
|
"""Run the interactive teach pendant loop.
|
|
715
732
|
|
|
@@ -880,17 +897,10 @@ def _teach_pendant(
|
|
|
880
897
|
# --- Freedrive ---
|
|
881
898
|
elif key == "f":
|
|
882
899
|
if not state["freedrive"]:
|
|
883
|
-
mode = state.get("_last_freedrive_mode", FreedriveMode.ALL)
|
|
884
|
-
if mode == FreedriveMode.ALL:
|
|
885
|
-
mode = FreedriveMode.XYZ
|
|
886
|
-
else:
|
|
887
|
-
mode = FreedriveMode.ALL
|
|
888
900
|
try:
|
|
889
|
-
robot.enable_freedrive(
|
|
901
|
+
robot.enable_freedrive(FreedriveMode.ALL)
|
|
890
902
|
state["freedrive"] = True
|
|
891
|
-
state["freedrive_mode"] =
|
|
892
|
-
state["_last_freedrive_mode"] = mode
|
|
893
|
-
messages.append(f"Freedrive {mode.name}")
|
|
903
|
+
state["freedrive_mode"] = FreedriveMode.ALL
|
|
894
904
|
except MotionError as e:
|
|
895
905
|
messages.append(f"Freedrive error: {e}")
|
|
896
906
|
else:
|
|
@@ -899,15 +909,12 @@ def _teach_pendant(
|
|
|
899
909
|
robot.disable_freedrive()
|
|
900
910
|
robot.enable_freedrive(FreedriveMode.XYZ)
|
|
901
911
|
state["freedrive_mode"] = FreedriveMode.XYZ
|
|
902
|
-
state["_last_freedrive_mode"] = FreedriveMode.XYZ
|
|
903
|
-
messages.append("Freedrive XYZ")
|
|
904
912
|
except MotionError as e:
|
|
905
913
|
messages.append(f"Freedrive error: {e}")
|
|
906
914
|
else:
|
|
907
915
|
try:
|
|
908
916
|
robot.disable_freedrive()
|
|
909
917
|
state["freedrive"] = False
|
|
910
|
-
messages.append("Freedrive OFF")
|
|
911
918
|
except MotionError as e:
|
|
912
919
|
messages.append(f"Freedrive error: {e}")
|
|
913
920
|
command_handled = True
|
|
@@ -1016,6 +1023,19 @@ def _teach_pendant(
|
|
|
1016
1023
|
messages.append(f"Error: {e}")
|
|
1017
1024
|
command_handled = True
|
|
1018
1025
|
|
|
1026
|
+
# --- Save config ---
|
|
1027
|
+
elif key == "y":
|
|
1028
|
+
save_cfg: dict = {}
|
|
1029
|
+
save_cfg["robot_ip"] = robot.ip
|
|
1030
|
+
if current_gripper_name:
|
|
1031
|
+
save_cfg["gripper"] = current_gripper_name
|
|
1032
|
+
if current_points_path:
|
|
1033
|
+
save_cfg["points_path"] = current_points_path
|
|
1034
|
+
_save_config(save_cfg, config_path)
|
|
1035
|
+
target = Path(config_path) if config_path else _resolve_default_config_path()
|
|
1036
|
+
messages.append(f"Config saved to {target}")
|
|
1037
|
+
command_handled = True
|
|
1038
|
+
|
|
1019
1039
|
# --- Redraw after movement or command ---
|
|
1020
1040
|
if moved or command_handled:
|
|
1021
1041
|
_draw_screen(robot, state, messages)
|
|
@@ -1058,12 +1078,15 @@ def teach_command(args) -> None:
|
|
|
1058
1078
|
else:
|
|
1059
1079
|
logging.basicConfig(level=logging.WARNING)
|
|
1060
1080
|
|
|
1061
|
-
# Load config
|
|
1062
|
-
config = load_config()
|
|
1081
|
+
# Load config
|
|
1082
|
+
config = load_config(args.config)
|
|
1063
1083
|
|
|
1064
1084
|
# Resolve effective values: CLI arg > config > None
|
|
1065
1085
|
ip = args.ip or config.get("robot_ip")
|
|
1066
1086
|
gripper_name = args.gripper or config.get("gripper")
|
|
1087
|
+
# "none" is an explicit CLI override — don't fall through to config
|
|
1088
|
+
if gripper_name and gripper_name.lower() == "none":
|
|
1089
|
+
gripper_name = None
|
|
1067
1090
|
points_path = args.points or config.get("points_path") or "points.db"
|
|
1068
1091
|
|
|
1069
1092
|
# Resolve gripper constructor params from config.yaml gripper_config section
|
|
@@ -1149,18 +1172,13 @@ def teach_command(args) -> None:
|
|
|
1149
1172
|
)
|
|
1150
1173
|
sys.exit(1)
|
|
1151
1174
|
|
|
1152
|
-
# Auto-save last-used params to config
|
|
1153
|
-
print(f" Saving config...", flush=True)
|
|
1154
|
-
save_cfg = dict(config)
|
|
1155
|
-
save_cfg["robot_ip"] = ip
|
|
1156
|
-
if gripper_name:
|
|
1157
|
-
save_cfg["gripper"] = gripper_name
|
|
1158
|
-
save_cfg["points_path"] = points_path
|
|
1159
|
-
_save_config(save_cfg)
|
|
1160
|
-
print(f" Config saved.", flush=True)
|
|
1161
|
-
|
|
1162
1175
|
try:
|
|
1163
|
-
_teach_pendant(
|
|
1176
|
+
_teach_pendant(
|
|
1177
|
+
robot,
|
|
1178
|
+
config_path=args.config,
|
|
1179
|
+
current_gripper_name=gripper_name,
|
|
1180
|
+
current_points_path=points_path,
|
|
1181
|
+
)
|
|
1164
1182
|
except KeyboardInterrupt:
|
|
1165
1183
|
pass
|
|
1166
1184
|
finally:
|
|
@@ -170,7 +170,7 @@ def _validate_connection(ip: str, timeout: float = 5.0) -> dict[str, object]:
|
|
|
170
170
|
def _connect_rtde(
|
|
171
171
|
ip: str,
|
|
172
172
|
*,
|
|
173
|
-
frequency: float =
|
|
173
|
+
frequency: float = 500.0,
|
|
174
174
|
max_wait: float = 30.0,
|
|
175
175
|
) -> Tuple[
|
|
176
176
|
"RTDEControlInterface",
|
|
@@ -193,7 +193,7 @@ def _connect_rtde(
|
|
|
193
193
|
|
|
194
194
|
Args:
|
|
195
195
|
ip: Robot IP address.
|
|
196
|
-
frequency: RTDE communication frequency (default
|
|
196
|
+
frequency: RTDE communication frequency (default 500 Hz).
|
|
197
197
|
max_wait: Maximum time (seconds) to spend retrying the RTDE
|
|
198
198
|
connection (default 30s).
|
|
199
199
|
|
|
@@ -296,7 +296,14 @@ def _connect_rtde(
|
|
|
296
296
|
f"EtherNet/IP, PROFINET, and MODBUS. Save and restart."
|
|
297
297
|
) from e
|
|
298
298
|
|
|
299
|
-
|
|
299
|
+
if "start control script" in err_msg.lower():
|
|
300
|
+
raise ConnectionError(
|
|
301
|
+
f"Failed to start the RTDE control script on the robot at {ip}. "
|
|
302
|
+
f"This usually means a program is already occupying the secondary interface. "
|
|
303
|
+
f"Stop any running program on the robot and try again."
|
|
304
|
+
) from e
|
|
305
|
+
|
|
306
|
+
# Retryable RuntimeError (e.g., transient connection issues)
|
|
300
307
|
remaining = max_wait - (time.time() - start)
|
|
301
308
|
if remaining <= 0:
|
|
302
309
|
raise ConnectionError(
|
|
@@ -13,11 +13,10 @@ from abc import ABC, abstractmethod
|
|
|
13
13
|
class Gripper(ABC):
|
|
14
14
|
"""Abstract base class for gripper backends.
|
|
15
15
|
|
|
16
|
-
Subclasses must implement ``open()``, ``close()``, ``set_position()
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
returns the appropriate backend.
|
|
16
|
+
Subclasses must implement ``open()``, ``close()``, and ``set_position()``.
|
|
17
|
+
The ``activate()`` method is a no-op by default and should be overridden
|
|
18
|
+
by backends that require explicit activation (e.g., Robotiq). The factory
|
|
19
|
+
method ``Gripper.create()`` returns the appropriate backend.
|
|
21
20
|
"""
|
|
22
21
|
|
|
23
22
|
@abstractmethod
|
|
@@ -71,13 +70,24 @@ class Gripper(ABC):
|
|
|
71
70
|
to re-enable the gripper.
|
|
72
71
|
"""
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
def get_position_mm(self) -> float | None:
|
|
74
|
+
"""Return the last commanded position in mm, or None if unknown.
|
|
75
|
+
|
|
76
|
+
Returns the most recently set position. Subclasses that support
|
|
77
|
+
reading the actual hardware position should override this method.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Position in mm, or None if not available.
|
|
81
|
+
"""
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
def max_travel_mm(self) -> float | None:
|
|
85
|
+
"""Return the maximum finger travel in mm, or None if unknown.
|
|
77
86
|
|
|
78
87
|
Returns:
|
|
79
|
-
|
|
88
|
+
Max travel in mm, or None.
|
|
80
89
|
"""
|
|
90
|
+
return None
|
|
81
91
|
|
|
82
92
|
@classmethod
|
|
83
93
|
def create(cls, name: str, **kwargs) -> "Gripper":
|
|
@@ -128,13 +128,3 @@ class DigitalGripper(Gripper):
|
|
|
128
128
|
raise GripperError(
|
|
129
129
|
"Digital grippers do not support set_speed."
|
|
130
130
|
)
|
|
131
|
-
|
|
132
|
-
def is_connected(self) -> bool:
|
|
133
|
-
"""Check if the gripper is connected.
|
|
134
|
-
|
|
135
|
-
Raises:
|
|
136
|
-
GripperError: Always — digital grippers can't verify connection.
|
|
137
|
-
"""
|
|
138
|
-
raise GripperError(
|
|
139
|
-
"Digital grippers do not support connection checks."
|
|
140
|
-
)
|
|
@@ -77,6 +77,7 @@ class RobotiqGripper(Gripper):
|
|
|
77
77
|
self._force = force
|
|
78
78
|
self._speed = speed
|
|
79
79
|
self._activated = False
|
|
80
|
+
self._last_position_mm: float | None = None
|
|
80
81
|
|
|
81
82
|
logger.info(
|
|
82
83
|
"RobotiqGripper initialized (preamble, max_mm=%d)", max_mm
|
|
@@ -160,7 +161,7 @@ class RobotiqGripper(Gripper):
|
|
|
160
161
|
"""
|
|
161
162
|
if self._activated:
|
|
162
163
|
return
|
|
163
|
-
# Check activation state and activate
|
|
164
|
+
# Check activation state and activate if needed.
|
|
164
165
|
# rq_activate_and_wait() sends ACT then polls until the gripper
|
|
165
166
|
# reports activated (~3-5s for internal initialization).
|
|
166
167
|
activation_script = (
|
|
@@ -170,7 +171,6 @@ class RobotiqGripper(Gripper):
|
|
|
170
171
|
+ "if (not rq_is_gripper_activated()):\n"
|
|
171
172
|
+ " rq_activate_and_wait()\n"
|
|
172
173
|
+ "end\n"
|
|
173
|
-
+ "rq_open_and_wait()\n"
|
|
174
174
|
)
|
|
175
175
|
# Run in a thread with a timeout — the preamble blocks indefinitely
|
|
176
176
|
# if no gripper is physically connected (2000-iteration loop).
|
|
@@ -204,10 +204,9 @@ class RobotiqGripper(Gripper):
|
|
|
204
204
|
def deactivate(self) -> None:
|
|
205
205
|
"""Deactivate the gripper (send DEACT command).
|
|
206
206
|
|
|
207
|
+
Sends the Robotiq DEACT command to power down the gripper.
|
|
207
208
|
Safe to call when not activated — becomes a no-op.
|
|
208
|
-
|
|
209
|
-
gripper's script from interfering with manual movement.
|
|
210
|
-
Call activate() again after freedrive is disabled.
|
|
209
|
+
Call activate() again to re-enable the gripper.
|
|
211
210
|
"""
|
|
212
211
|
if not self._activated:
|
|
213
212
|
return
|
|
@@ -234,6 +233,7 @@ class RobotiqGripper(Gripper):
|
|
|
234
233
|
self._require_activated()
|
|
235
234
|
func = "rq_open_and_wait()" if wait else "rq_open()"
|
|
236
235
|
self._send_script(self._build_script(func))
|
|
236
|
+
self._last_position_mm = float(self._max_mm)
|
|
237
237
|
logger.debug("Robotiq gripper opened (wait=%s)", wait)
|
|
238
238
|
|
|
239
239
|
def close(self, *, wait: bool = True) -> None:
|
|
@@ -250,6 +250,7 @@ class RobotiqGripper(Gripper):
|
|
|
250
250
|
self._require_activated()
|
|
251
251
|
func = "rq_close_and_wait()" if wait else "rq_close()"
|
|
252
252
|
self._send_script(self._build_script(func))
|
|
253
|
+
self._last_position_mm = 0.0
|
|
253
254
|
logger.debug("Robotiq gripper closed (wait=%s)", wait)
|
|
254
255
|
|
|
255
256
|
def set_position(self, mm: float, *, wait: bool = True) -> None:
|
|
@@ -275,6 +276,7 @@ class RobotiqGripper(Gripper):
|
|
|
275
276
|
else:
|
|
276
277
|
func = f"rq_move_mm({mm})"
|
|
277
278
|
self._send_script(self._build_script(func))
|
|
279
|
+
self._last_position_mm = mm
|
|
278
280
|
logger.debug("Robotiq gripper set to %.1f mm (wait=%s)", mm, wait)
|
|
279
281
|
|
|
280
282
|
def set_force(self, force: int) -> None:
|
|
@@ -303,15 +305,6 @@ class RobotiqGripper(Gripper):
|
|
|
303
305
|
self._speed = speed
|
|
304
306
|
logger.debug("Robotiq gripper speed set to %d", speed)
|
|
305
307
|
|
|
306
|
-
def is_connected(self) -> bool:
|
|
307
|
-
"""Check if the gripper can send commands via RTDE."""
|
|
308
|
-
try:
|
|
309
|
-
return self._rtde.sendCustomScriptFunction(
|
|
310
|
-
"_grip_ping", 'textmsg("gripper", "ok")'
|
|
311
|
-
)
|
|
312
|
-
except Exception:
|
|
313
|
-
return False
|
|
314
|
-
|
|
315
308
|
def disconnect(self) -> None:
|
|
316
309
|
"""Disconnect the gripper.
|
|
317
310
|
|
|
@@ -319,4 +312,18 @@ class RobotiqGripper(Gripper):
|
|
|
319
312
|
using the gripper.
|
|
320
313
|
"""
|
|
321
314
|
self._activated = False
|
|
315
|
+
self._last_position_mm = None
|
|
322
316
|
logger.debug("Robotiq gripper disconnected")
|
|
317
|
+
|
|
318
|
+
def get_position_mm(self) -> float | None:
|
|
319
|
+
"""Return the last commanded position in mm.
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
Position in mm (0 = closed, max_mm = open), or None if
|
|
323
|
+
no position has been set yet.
|
|
324
|
+
"""
|
|
325
|
+
return self._last_position_mm
|
|
326
|
+
|
|
327
|
+
def max_travel_mm(self) -> float:
|
|
328
|
+
"""Return the maximum finger travel in mm."""
|
|
329
|
+
return float(self._max_mm)
|
|
@@ -57,7 +57,7 @@ class FreedriveMode(IntEnum):
|
|
|
57
57
|
"""
|
|
58
58
|
|
|
59
59
|
ALL = 0 # All 6 axes free
|
|
60
|
-
XYZ = 1 #
|
|
60
|
+
XYZ = 1 # Linear axes (X, Y, Z) + rotation around Z (Rz)
|
|
61
61
|
ROTATION = 2 # Only rotational axes (Roll, Pitch, Yaw)
|
|
62
62
|
|
|
63
63
|
|
|
@@ -475,7 +475,7 @@ class Motion:
|
|
|
475
475
|
if mode == FreedriveMode.ALL:
|
|
476
476
|
free_axes = [1, 1, 1, 1, 1, 1]
|
|
477
477
|
elif mode == FreedriveMode.XYZ:
|
|
478
|
-
free_axes = [1, 1, 1, 0, 0,
|
|
478
|
+
free_axes = [1, 1, 1, 0, 0, 1]
|
|
479
479
|
elif mode == FreedriveMode.ROTATION:
|
|
480
480
|
free_axes = [0, 0, 0, 1, 1, 1]
|
|
481
481
|
else:
|
|
@@ -76,7 +76,6 @@ class URRobot:
|
|
|
76
76
|
Presets provide mass, CoG, TCP offset, and backend type.
|
|
77
77
|
default_vel: Default linear velocity (m/s).
|
|
78
78
|
default_acc: Default linear acceleration (m/s²).
|
|
79
|
-
rtde_frequency: RTDE communication frequency in Hz (default 125).
|
|
80
79
|
gripper_kwargs: Additional kwargs passed to the gripper backend
|
|
81
80
|
to override preset values (e.g. ``max_mm=80`` for custom
|
|
82
81
|
fingers, ``force=50``, ``speed=80`` for Robotiq).
|
|
@@ -97,13 +96,12 @@ class URRobot:
|
|
|
97
96
|
gripper: GripperPreset | DigitalGripperConfig | None = None,
|
|
98
97
|
default_vel: float = 0.5,
|
|
99
98
|
default_acc: float = 0.3,
|
|
100
|
-
rtde_frequency: float = 125.0,
|
|
101
99
|
**gripper_kwargs,
|
|
102
100
|
) -> None:
|
|
103
101
|
self._ip = ip
|
|
104
102
|
self._default_vel = default_vel
|
|
105
103
|
self._default_acc = default_acc
|
|
106
|
-
self._rtde_frequency =
|
|
104
|
+
self._rtde_frequency = 500.0
|
|
107
105
|
self._connection_lost = False
|
|
108
106
|
self._move_frame: MoveFrame = MoveFrame.BASE
|
|
109
107
|
|
|
@@ -298,7 +296,6 @@ class URRobot:
|
|
|
298
296
|
gripper: GripperPreset | DigitalGripperConfig | str | None = None,
|
|
299
297
|
default_vel: float | None = None,
|
|
300
298
|
default_acc: float | None = None,
|
|
301
|
-
rtde_frequency: float | None = None,
|
|
302
299
|
**gripper_kwargs,
|
|
303
300
|
) -> "URRobot":
|
|
304
301
|
"""Create a URRobot from a YAML config file or dict.
|
|
@@ -315,7 +312,6 @@ class URRobot:
|
|
|
315
312
|
or ``None``. Overrides the ``gripper`` key from config.
|
|
316
313
|
default_vel: Default linear velocity (m/s).
|
|
317
314
|
default_acc: Default linear acceleration (m/s²).
|
|
318
|
-
rtde_frequency: RTDE communication frequency in Hz.
|
|
319
315
|
gripper_kwargs: Overrides for gripper preset values
|
|
320
316
|
(e.g. ``max_mm``, ``force``, ``speed``, ``pin``).
|
|
321
317
|
|
|
@@ -326,7 +322,7 @@ class URRobot:
|
|
|
326
322
|
gripper: hand-e
|
|
327
323
|
default_vel: 0.5
|
|
328
324
|
default_acc: 0.3
|
|
329
|
-
rtde_frequency:
|
|
325
|
+
rtde_frequency: 500
|
|
330
326
|
|
|
331
327
|
Example:
|
|
332
328
|
>>> robot = URRobot.from_config("config.yaml")
|
|
@@ -394,7 +390,6 @@ class URRobot:
|
|
|
394
390
|
gripper=resolved_gripper,
|
|
395
391
|
default_vel=default_vel if default_vel is not None else cfg.get("default_vel", 0.5),
|
|
396
392
|
default_acc=default_acc if default_acc is not None else cfg.get("default_acc", 0.3),
|
|
397
|
-
rtde_frequency=rtde_frequency if rtde_frequency is not None else cfg.get("rtde_frequency", 125.0),
|
|
398
393
|
**gripper_kwargs,
|
|
399
394
|
)
|
|
400
395
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: urkit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Universal Robots e-Series control toolkit built on ur_rtde
|
|
5
5
|
Author: URKit Contributors
|
|
6
6
|
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/rodolfo-verde/urkit
|
|
7
8
|
Keywords: universal-robots,ur3e,ur5e,ur7e,ur10e,ur12e,ur16e,ur8,ur15,ur18,ur20,ur30,rtde,robotics,industrial-robot
|
|
8
9
|
Classifier: Development Status :: 3 - Alpha
|
|
9
10
|
Classifier: Intended Audience :: Developers
|
|
@@ -25,6 +26,8 @@ Requires-Dist: rich>=13.0.0
|
|
|
25
26
|
|
|
26
27
|
# URKit
|
|
27
28
|
|
|
29
|
+
[](https://pypi.org/project/urkit/)
|
|
30
|
+
|
|
28
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.
|
|
29
32
|
|
|
30
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. It doesn't try to replace `ur_rtde`; it sits on top of it so you can use both in tandem.
|
|
@@ -103,17 +106,20 @@ urkit teach # reads IP from config.yaml
|
|
|
103
106
|
| Flag | Description |
|
|
104
107
|
|------|-------------|
|
|
105
108
|
| `ip` | Robot IP address (positional, overrides config) |
|
|
106
|
-
| `--gripper` | Gripper preset: `2f-85`, `2f-140`, `hand-e`, `digital` |
|
|
109
|
+
| `--gripper` | Gripper preset: `2f-85`, `2f-140`, `hand-e`, `digital`, `none` |
|
|
107
110
|
| `--gripper-pin` | Digital gripper output pin (default: 0) |
|
|
108
111
|
| `--gripper-force` | Robotiq force 0-100 |
|
|
109
112
|
| `--gripper-speed` | Robotiq speed 0-100 |
|
|
110
113
|
| `--gripper-close-on-high` | Digital polarity: `true` or `false` |
|
|
111
114
|
| `--points` | Path to `points.db` file (overrides config) |
|
|
115
|
+
| `--config` | Path to config file (default: `config.yaml` in project root or CWD) |
|
|
112
116
|
| `-v`, `--verbose` | Show verbose output (debug connection issues) |
|
|
113
117
|
|
|
114
118
|
```bash
|
|
115
119
|
urkit teach 172.31.1.200 --gripper hand-e --points /path/to/points.db
|
|
116
120
|
urkit teach --gripper digital --gripper-pin 3
|
|
121
|
+
urkit teach --gripper none # no gripper (overrides config)
|
|
122
|
+
urkit teach --config /path/to/my.yaml # load custom config
|
|
117
123
|
urkit teach -v # verbose mode
|
|
118
124
|
```
|
|
119
125
|
|
|
@@ -210,10 +216,11 @@ All movement and orientation keys support **hold-to-repeat** — hold a key down
|
|
|
210
216
|
<td align="center" style="width:50%">
|
|
211
217
|
<table>
|
|
212
218
|
<tr><th>Key</th><th>Action</th></tr>
|
|
213
|
-
<tr><td><code>F</code></td><td>Freedrive (OFF → ALL → XYZ)</td></tr>
|
|
219
|
+
<tr><td><code>F</code></td><td>Freedrive (OFF → ALL → XYZ+Rz)</td></tr>
|
|
214
220
|
<tr><td><code>M</code></td><td>Toggle frame (BASE / TOOL)</td></tr>
|
|
215
221
|
<tr><td><code>N</code></td><td>Go To mode (Cartesian / Joint)</td></tr>
|
|
216
222
|
<tr><td><code>T</code></td><td>Orient TCP down (180°)</td></tr>
|
|
223
|
+
<tr><td><code>Y</code></td><td>Save config to file</td></tr>
|
|
217
224
|
<tr><td><code>ESC</code></td><td>Exit</td></tr>
|
|
218
225
|
</table>
|
|
219
226
|
</td>
|
|
@@ -249,22 +256,110 @@ robot = URRobot(
|
|
|
249
256
|
|
|
250
257
|
### From Config
|
|
251
258
|
|
|
252
|
-
|
|
259
|
+
```python
|
|
260
|
+
robot = URRobot.from_config("config.yaml")
|
|
261
|
+
robot = URRobot.from_config("config.yaml", ip="10.0.0.50") # override IP
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
See the [Configuration](#configuration) section for full details on config location, keys, and saving.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Configuration
|
|
269
|
+
|
|
270
|
+
URKit uses a YAML config file (`config.yaml`) to persist settings between sessions. The CLI reads it automatically, and `URRobot.from_config()` loads it programmatically.
|
|
271
|
+
|
|
272
|
+
### Config File Location
|
|
273
|
+
|
|
274
|
+
URKit searches for `config.yaml` in this order:
|
|
275
|
+
1. Explicit path via `--config` flag or `load_config("path")`
|
|
276
|
+
2. Project root (where `src/urkit` lives)
|
|
277
|
+
3. Current working directory
|
|
278
|
+
|
|
279
|
+
If no config file exists, URKit uses built-in defaults and operates fine — the config is optional.
|
|
280
|
+
|
|
281
|
+
### Config Keys
|
|
282
|
+
|
|
283
|
+
| Key | Description | Example |
|
|
284
|
+
|-----|-------------|---------|
|
|
285
|
+
| `robot_ip` | Robot IP address | `192.168.1.100` |
|
|
286
|
+
| `points_path` | Path to SQLite points database | `points.db` |
|
|
287
|
+
| `gripper` | Gripper preset name | `hand-e`, `2f-85`, `2f-140`, `digital` |
|
|
288
|
+
| `default_vel` | Default linear velocity (m/s) | `0.5` |
|
|
289
|
+
| `default_acc` | Default linear acceleration (m/s²) | `0.3` |
|
|
290
|
+
| `rtde_frequency` | RTDE communication frequency (Hz) | `125` |
|
|
291
|
+
| `rtde_frequency` | RTDE communication frequency (Hz) | `125` |
|
|
292
|
+
| `rtde_frequency` | RTDE communication frequency (Hz) | `125` |
|
|
293
|
+
| `rtde_frequency` | RTDE communication frequency (Hz) | `125` |
|
|
294
|
+
| `rtde_frequency` | RTDE communication frequency (Hz) | `125` |
|
|
295
|
+
|
|
296
|
+
### Gripper Config Section
|
|
297
|
+
|
|
298
|
+
For digital grippers, specify pin and polarity:
|
|
299
|
+
|
|
300
|
+
```yaml
|
|
301
|
+
gripper: digital
|
|
302
|
+
gripper_config:
|
|
303
|
+
pin: 3
|
|
304
|
+
close_on_high: true
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
For Robotiq grippers, override preset values:
|
|
253
308
|
|
|
254
309
|
```yaml
|
|
255
|
-
robot_ip: 172.31.1.200
|
|
256
|
-
points_path: points.db
|
|
257
310
|
gripper: hand-e
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
311
|
+
gripper_config:
|
|
312
|
+
force: 50
|
|
313
|
+
speed: 80
|
|
261
314
|
```
|
|
262
315
|
|
|
263
|
-
|
|
316
|
+
### CLI Override Precedence
|
|
317
|
+
|
|
318
|
+
Settings are resolved in this order (highest priority first):
|
|
319
|
+
|
|
320
|
+
1. **CLI flags** — `urkit teach 172.31.1.200 --gripper none`
|
|
321
|
+
2. **Config file** — values from `config.yaml`
|
|
322
|
+
3. **Built-in defaults** — `points.db` for points, no gripper, `0.5` m/s velocity
|
|
323
|
+
|
|
324
|
+
Use `--gripper none` to explicitly disable a gripper that's set in your config file.
|
|
325
|
+
|
|
326
|
+
### Saving Config
|
|
327
|
+
|
|
328
|
+
The CLI **never** modifies your config file automatically. Inside the teach pendant, press **Y** to save your current session's settings (IP, gripper, points path) to the config file. This way you only save settings you've actually tested and verified work.
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# First connection — test everything, then press Y inside the pendant
|
|
332
|
+
urkit teach 172.31.1.200 --gripper hand-e
|
|
333
|
+
|
|
334
|
+
# After pressing Y, config.yaml is saved. Next time:
|
|
335
|
+
urkit teach # reads IP + gripper from config
|
|
336
|
+
|
|
337
|
+
# Custom config file
|
|
338
|
+
urkit teach --config station_a.yaml # load from custom path
|
|
339
|
+
# press Y inside → saves back to station_a.yaml
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
This lets you maintain separate configs per workcell:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
urkit teach --config station_a.yaml # press Y to save
|
|
346
|
+
urkit teach --config station_b.yaml # press Y to save
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Programmatic Config
|
|
264
350
|
|
|
265
351
|
```python
|
|
266
|
-
|
|
267
|
-
|
|
352
|
+
from urkit import load_config, resolve_config
|
|
353
|
+
|
|
354
|
+
# Load with auto-resolution
|
|
355
|
+
config = load_config() # searches for config.yaml
|
|
356
|
+
config = load_config("/path/to/my.yaml") # explicit path
|
|
357
|
+
|
|
358
|
+
# Check if config exists
|
|
359
|
+
path = resolve_config() # returns Path or None
|
|
360
|
+
|
|
361
|
+
# Create robot from config dict
|
|
362
|
+
robot = URRobot.from_config({"robot_ip": "172.31.1.200", "gripper": "2f-85"})
|
|
268
363
|
```
|
|
269
364
|
|
|
270
365
|
---
|
|
@@ -478,7 +573,7 @@ from urkit import FreedriveMode
|
|
|
478
573
|
|
|
479
574
|
# Freedrive lets you manually push the robot: motion commands won't work while active
|
|
480
575
|
robot.enable_freedrive() # all 6 axes free
|
|
481
|
-
robot.enable_freedrive(FreedriveMode.XYZ) # linear axes
|
|
576
|
+
robot.enable_freedrive(FreedriveMode.XYZ) # linear axes + Rz rotation
|
|
482
577
|
robot.enable_freedrive(FreedriveMode.ROTATION) # rotation only
|
|
483
578
|
robot.disable_freedrive() # always disable before sending motion commands
|
|
484
579
|
|
|
@@ -116,6 +116,49 @@ class TestRobotiqActivation:
|
|
|
116
116
|
with pytest.raises(GripperError, match="not activated"):
|
|
117
117
|
self.gripper.open()
|
|
118
118
|
|
|
119
|
+
def test_get_position_mm_initially_none(self):
|
|
120
|
+
"""get_position_mm() returns None before any operation."""
|
|
121
|
+
assert self.gripper.get_position_mm() is None
|
|
122
|
+
|
|
123
|
+
def test_get_position_mm_after_activate(self):
|
|
124
|
+
"""get_position_mm() returns None after activation (gripper doesn't open)."""
|
|
125
|
+
self.gripper.activate()
|
|
126
|
+
assert self.gripper.get_position_mm() is None
|
|
127
|
+
|
|
128
|
+
def test_get_position_mm_after_open(self):
|
|
129
|
+
"""get_position_mm() returns max_mm after open()."""
|
|
130
|
+
self.gripper.activate()
|
|
131
|
+
self.gripper.open()
|
|
132
|
+
assert self.gripper.get_position_mm() == 50.0
|
|
133
|
+
|
|
134
|
+
def test_get_position_mm_after_close(self):
|
|
135
|
+
"""get_position_mm() returns 0 after close()."""
|
|
136
|
+
self.gripper.activate()
|
|
137
|
+
self.gripper.close()
|
|
138
|
+
assert self.gripper.get_position_mm() == 0.0
|
|
139
|
+
|
|
140
|
+
def test_get_position_mm_after_set_position(self):
|
|
141
|
+
"""get_position_mm() returns the commanded position."""
|
|
142
|
+
self.gripper.activate()
|
|
143
|
+
self.gripper.set_position(25)
|
|
144
|
+
assert self.gripper.get_position_mm() == 25.0
|
|
145
|
+
|
|
146
|
+
def test_get_position_mm_after_disconnect(self):
|
|
147
|
+
"""get_position_mm() returns None after disconnect."""
|
|
148
|
+
self.gripper.activate()
|
|
149
|
+
self.gripper.set_position(30)
|
|
150
|
+
self.gripper.disconnect()
|
|
151
|
+
assert self.gripper.get_position_mm() is None
|
|
152
|
+
|
|
153
|
+
def test_max_travel_mm(self):
|
|
154
|
+
"""max_travel_mm() returns the configured max_mm."""
|
|
155
|
+
assert self.gripper.max_travel_mm() == 50.0
|
|
156
|
+
|
|
157
|
+
def test_max_travel_mm_custom(self):
|
|
158
|
+
"""max_travel_mm() returns the custom max_mm value."""
|
|
159
|
+
gripper = RobotiqGripper(rtde_control=self.rtde, max_mm=140)
|
|
160
|
+
assert gripper.max_travel_mm() == 140.0
|
|
161
|
+
|
|
119
162
|
|
|
120
163
|
# ------------------------------------------------------------------
|
|
121
164
|
# DigitalGripper activation tests
|
|
@@ -146,11 +189,6 @@ class TestDigitalActivation:
|
|
|
146
189
|
gripper.close()
|
|
147
190
|
mock_rtde.setStandardDigitalOut.assert_called_with(1, False)
|
|
148
191
|
|
|
149
|
-
def test_is_connected_raises(self, mock_rtde, mock_rtde_r):
|
|
150
|
-
gripper = DigitalGripper(mock_rtde, mock_rtde_r, pin=0)
|
|
151
|
-
with pytest.raises(GripperError, match="do not support connection"):
|
|
152
|
-
gripper.is_connected()
|
|
153
|
-
|
|
154
192
|
def test_set_position_raises(self, mock_rtde, mock_rtde_r):
|
|
155
193
|
gripper = DigitalGripper(mock_rtde, mock_rtde_r, pin=0)
|
|
156
194
|
with pytest.raises(GripperError, match="do not support set_position"):
|
|
@@ -78,13 +78,12 @@ class TestRobotiqGripper:
|
|
|
78
78
|
self.gripper.activate()
|
|
79
79
|
self.gripper.open()
|
|
80
80
|
calls = self.rtde.sendCustomScriptFunction.call_args_list
|
|
81
|
-
# activate (
|
|
81
|
+
# activate (check+activate only) + open = 2 calls
|
|
82
82
|
assert len(calls) == 2
|
|
83
83
|
# Check the activate call contains activation logic
|
|
84
84
|
activate_code = calls[0][0][1]
|
|
85
85
|
assert "rq_is_gripper_activated()" in activate_code
|
|
86
86
|
assert "rq_activate_and_wait()" in activate_code
|
|
87
|
-
assert "rq_open_and_wait()" in activate_code
|
|
88
87
|
# Check the open-after-activate call contains rq_open_and_wait
|
|
89
88
|
open_code = calls[1][0][1]
|
|
90
89
|
assert "rq_open_and_wait()" in open_code
|
|
@@ -131,9 +130,6 @@ class TestRobotiqGripper:
|
|
|
131
130
|
second_count = self.rtde.sendCustomScriptFunction.call_count
|
|
132
131
|
assert second_count - first_count == 1
|
|
133
132
|
|
|
134
|
-
def test_is_connected(self):
|
|
135
|
-
assert self.gripper.is_connected() is True
|
|
136
|
-
|
|
137
133
|
def test_disconnect_resets_activation(self):
|
|
138
134
|
"""disconnect() resets activation flag; must call activate() again."""
|
|
139
135
|
self.gripper.activate()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|