urkit 0.4.5__tar.gz → 0.4.6__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.4.5 → urkit-0.4.6}/PKG-INFO +1 -1
- {urkit-0.4.5 → urkit-0.4.6}/pyproject.toml +1 -1
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/__init__.py +1 -1
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/cli/points.py +2 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/cli/teach.py +14 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/cli/terminal.py +44 -6
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/motion.py +36 -6
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit.egg-info/PKG-INFO +1 -1
- {urkit-0.4.5 → urkit-0.4.6}/README.md +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/setup.cfg +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/__main__.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/cli/__init__.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/cli/colors.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/cli/connection_monitor.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/cli/init.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/config.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/connection.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/exceptions.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/geometry.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/gripper/__init__.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/gripper/base.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/gripper/digital.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/gripper/presets.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/gripper/robotiq.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/gripper/robotiq_preamble.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/io.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/points.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/robot.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit/telemetry.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit.egg-info/SOURCES.txt +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit.egg-info/dependency_links.txt +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit.egg-info/entry_points.txt +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit.egg-info/requires.txt +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/src/urkit.egg-info/top_level.txt +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/tests/test_exceptions.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/tests/test_geometry.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/tests/test_gripper.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/tests/test_gripper_factory.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/tests/test_gripper_presets.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/tests/test_move_sequence.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/tests/test_points.py +0 -0
- {urkit-0.4.5 → urkit-0.4.6}/tests/test_robot_integration.py +0 -0
|
@@ -16,6 +16,7 @@ from pathlib import Path
|
|
|
16
16
|
from rich.console import Console
|
|
17
17
|
from rich.table import Table
|
|
18
18
|
|
|
19
|
+
from urkit import __version__
|
|
19
20
|
from urkit.config import _load_config as load_config
|
|
20
21
|
from urkit.cli.colors import blue, cyan, dim, yellow
|
|
21
22
|
from urkit.cli.terminal import RawTerminal, read_burst, wait_input, warn_if_windows
|
|
@@ -158,6 +159,7 @@ def _interactive_points_filter(points_db: Points, all_points: list[str], points_
|
|
|
158
159
|
# Clear screen and draw header
|
|
159
160
|
sys.stdout.write("\033[2J\033[1;1H")
|
|
160
161
|
sys.stdout.write(cyan(" === POINT EXPLORER ===") + "\n")
|
|
162
|
+
sys.stdout.write(dim(f" urkit {__version__}") + "\n")
|
|
161
163
|
sys.stdout.write(dim(" Type to search · ↑↓ scroll · Enter reload · ESC quit") + "\n\n")
|
|
162
164
|
sys.stdout.write(f" {blue('Search:')} {filter_str}\n")
|
|
163
165
|
if refresh_error:
|
|
@@ -19,16 +19,19 @@ import os
|
|
|
19
19
|
import signal
|
|
20
20
|
import sys
|
|
21
21
|
import time
|
|
22
|
+
import traceback
|
|
22
23
|
from pathlib import Path
|
|
23
24
|
|
|
24
25
|
import yaml
|
|
25
26
|
|
|
27
|
+
from urkit import __version__
|
|
26
28
|
from urkit.cli.colors import blue, cyan, dim, green, red, yellow
|
|
27
29
|
from urkit.cli.terminal import (
|
|
28
30
|
RawTerminal,
|
|
29
31
|
input_active,
|
|
30
32
|
read_burst,
|
|
31
33
|
start_interrupt_watcher,
|
|
34
|
+
stop_interrupt_watcher,
|
|
32
35
|
wait_input,
|
|
33
36
|
warn_if_windows,
|
|
34
37
|
)
|
|
@@ -297,6 +300,7 @@ def _draw_screen(
|
|
|
297
300
|
# Header
|
|
298
301
|
lines.append(dim("=" * width))
|
|
299
302
|
lines.append(cyan(" === URKit Teach Pendant ===").center(width))
|
|
303
|
+
lines.append(dim(f" urkit {__version__}").center(width))
|
|
300
304
|
lines.append(cyan(f" IP: {robot.ip}").center(width))
|
|
301
305
|
if expert_mode:
|
|
302
306
|
lines.append(yellow(" EXPERT MODE").center(width))
|
|
@@ -773,6 +777,9 @@ def _submenu_goto_point(
|
|
|
773
777
|
except URKitConnectionError:
|
|
774
778
|
raise
|
|
775
779
|
except Exception as e:
|
|
780
|
+
logger.debug(
|
|
781
|
+
"goto move failed: %s\n%s", e, traceback.format_exc()
|
|
782
|
+
)
|
|
776
783
|
messages.append(f"Error: {e}")
|
|
777
784
|
|
|
778
785
|
|
|
@@ -1321,6 +1328,9 @@ def _teach_pendant(
|
|
|
1321
1328
|
|
|
1322
1329
|
# Exit
|
|
1323
1330
|
if key == "\x1b" or key == "\x03":
|
|
1331
|
+
# The main loop owns cleanup from here; the watcher
|
|
1332
|
+
# must not fire a second stop/restore in parallel.
|
|
1333
|
+
stop_interrupt_watcher()
|
|
1324
1334
|
if state["freedrive"]:
|
|
1325
1335
|
try:
|
|
1326
1336
|
robot.disable_freedrive()
|
|
@@ -1366,6 +1376,9 @@ def _teach_pendant(
|
|
|
1366
1376
|
moved = True
|
|
1367
1377
|
last_move_t = time.monotonic()
|
|
1368
1378
|
except MotionError as e:
|
|
1379
|
+
logger.debug(
|
|
1380
|
+
"move key %r failed: %s\n%s", key, e, traceback.format_exc()
|
|
1381
|
+
)
|
|
1369
1382
|
messages.append(f"Error: {e}")
|
|
1370
1383
|
|
|
1371
1384
|
# --- Step size ---
|
|
@@ -1576,6 +1589,7 @@ def _teach_pendant(
|
|
|
1576
1589
|
print(f"\n{red('Fault:')} {e}")
|
|
1577
1590
|
finally:
|
|
1578
1591
|
# Stop monitor and restore signal handler
|
|
1592
|
+
stop_interrupt_watcher()
|
|
1579
1593
|
monitor.stop()
|
|
1580
1594
|
if _old_sigalrm is not None:
|
|
1581
1595
|
signal.signal(signal.SIGALRM, _old_sigalrm)
|
|
@@ -135,6 +135,18 @@ the main reader. Callers set it around their read sections and clear it
|
|
|
135
135
|
afterwards."""
|
|
136
136
|
|
|
137
137
|
|
|
138
|
+
watcher_stop = threading.Event()
|
|
139
|
+
"""Set when the CLI begins its own shutdown (normal exit, fault exit,
|
|
140
|
+
Ctrl+C in the main loop). The Windows interrupt watcher exits quietly
|
|
141
|
+
without firing, so it cannot race the main thread's cleanup (double
|
|
142
|
+
robot stop, double terminal restore)."""
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def stop_interrupt_watcher() -> None:
|
|
146
|
+
"""Tell the interrupt watcher to exit quietly. No-op on non-Windows."""
|
|
147
|
+
watcher_stop.set()
|
|
148
|
+
|
|
149
|
+
|
|
138
150
|
def start_interrupt_watcher(
|
|
139
151
|
on_fire: Callable[[str], None],
|
|
140
152
|
fault_reason: Callable[[], str | None],
|
|
@@ -163,9 +175,24 @@ def start_interrupt_watcher(
|
|
|
163
175
|
# guard and cannot see the module inside this closure.
|
|
164
176
|
import msvcrt
|
|
165
177
|
|
|
178
|
+
def _where_blocked() -> None:
|
|
179
|
+
# Diagnostic: report where the main thread was stuck so the
|
|
180
|
+
# next occurrence can be diagnosed (usually a blocking socket
|
|
181
|
+
# read inside ur_rtde with no timeout).
|
|
182
|
+
frame = sys._current_frames().get(threading.main_thread().ident)
|
|
183
|
+
if frame is None:
|
|
184
|
+
return
|
|
185
|
+
code = frame.f_code
|
|
186
|
+
sys.stderr.write(
|
|
187
|
+
"[urkit] main thread blocked in "
|
|
188
|
+
f"{os.path.basename(code.co_filename)}:{frame.f_lineno} "
|
|
189
|
+
f"in {code.co_name}\n"
|
|
190
|
+
)
|
|
191
|
+
sys.stderr.flush()
|
|
192
|
+
|
|
166
193
|
def _run() -> None:
|
|
167
194
|
backoff = 0.0
|
|
168
|
-
while
|
|
195
|
+
while not watcher_stop.is_set():
|
|
169
196
|
if backoff > 0:
|
|
170
197
|
time.sleep(backoff)
|
|
171
198
|
backoff = 0.0
|
|
@@ -174,18 +201,29 @@ def start_interrupt_watcher(
|
|
|
174
201
|
continue
|
|
175
202
|
reason = fault_reason()
|
|
176
203
|
if reason is not None:
|
|
204
|
+
_where_blocked()
|
|
177
205
|
on_fire(reason)
|
|
178
206
|
return
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
207
|
+
try:
|
|
208
|
+
if not msvcrt.kbhit():
|
|
209
|
+
time.sleep(0.05)
|
|
210
|
+
continue
|
|
211
|
+
byte = msvcrt.getch()
|
|
212
|
+
except OSError:
|
|
213
|
+
# Console is closing (shutdown in progress). The watcher
|
|
214
|
+
# is best-effort; exit quietly instead of dying with a
|
|
215
|
+
# traceback that races the main thread's exit.
|
|
216
|
+
return
|
|
183
217
|
if byte == b"\x03":
|
|
218
|
+
_where_blocked()
|
|
184
219
|
on_fire("Interrupted.")
|
|
185
220
|
return
|
|
186
221
|
# Not Ctrl+C: give the byte back and back off so the main
|
|
187
222
|
# reader (resuming from a blocking call) processes it.
|
|
188
|
-
|
|
223
|
+
try:
|
|
224
|
+
msvcrt.ungetch(byte[0])
|
|
225
|
+
except OSError:
|
|
226
|
+
return
|
|
189
227
|
backoff = 0.2
|
|
190
228
|
|
|
191
229
|
watcher = threading.Thread(
|
|
@@ -30,17 +30,26 @@ def _suppress_rtde_stderr() -> Iterator[None]:
|
|
|
30
30
|
to raw stderr (fd 2) on every call when the script is stopped.
|
|
31
31
|
This context manager suppresses those messages by redirecting fd 2
|
|
32
32
|
around the call.
|
|
33
|
+
|
|
34
|
+
Diagnostic: set URKIT_RTDE_STDERR=<path> to capture the suppressed
|
|
35
|
+
output in a file (append mode) instead. ur_rtde move failures are
|
|
36
|
+
reported there and nowhere else, so this is the only way to see
|
|
37
|
+
why a move was rejected.
|
|
33
38
|
"""
|
|
34
|
-
|
|
39
|
+
capture_path = os.environ.get("URKIT_RTDE_STDERR")
|
|
40
|
+
if capture_path:
|
|
41
|
+
target_fd = os.open(capture_path, os.O_WRONLY | os.O_CREAT | os.O_APPEND)
|
|
42
|
+
else:
|
|
43
|
+
target_fd = os.open(os.devnull, os.O_WRONLY)
|
|
35
44
|
old_stderr = os.dup(2)
|
|
36
45
|
try:
|
|
37
|
-
os.dup2(
|
|
46
|
+
os.dup2(target_fd, 2)
|
|
38
47
|
sys.stderr.flush()
|
|
39
48
|
yield
|
|
40
49
|
finally:
|
|
41
50
|
os.dup2(old_stderr, 2)
|
|
42
51
|
os.close(old_stderr)
|
|
43
|
-
os.close(
|
|
52
|
+
os.close(target_fd)
|
|
44
53
|
|
|
45
54
|
if TYPE_CHECKING:
|
|
46
55
|
from rtde_control import RTDEControlInterface
|
|
@@ -151,7 +160,14 @@ class Motion:
|
|
|
151
160
|
"movej: joints=%s, vel=%.3f, acc=%.3f, async=%s", joints, vel, acc, asynchronous
|
|
152
161
|
)
|
|
153
162
|
with _suppress_rtde_stderr():
|
|
154
|
-
self._rtde_c.moveJ(joints, vel, acc, asynchronous=asynchronous)
|
|
163
|
+
ok = self._rtde_c.moveJ(joints, vel, acc, asynchronous=asynchronous)
|
|
164
|
+
if ok is False:
|
|
165
|
+
raise MotionError(
|
|
166
|
+
"moveJ rejected by the controller: the RTDE control script "
|
|
167
|
+
"stopped, the robot entered a stop state, or the command "
|
|
168
|
+
"timed out. Set URKIT_RTDE_STDERR=<file> to capture "
|
|
169
|
+
"ur_rtde's error output."
|
|
170
|
+
)
|
|
155
171
|
except MotionError:
|
|
156
172
|
raise
|
|
157
173
|
except Exception as e:
|
|
@@ -197,7 +213,14 @@ class Motion:
|
|
|
197
213
|
"movel: pose=%s, vel=%.3f, acc=%.3f, async=%s", pose, vel, acc, asynchronous
|
|
198
214
|
)
|
|
199
215
|
with _suppress_rtde_stderr():
|
|
200
|
-
self._rtde_c.moveL(pose, vel, acc, asynchronous=asynchronous)
|
|
216
|
+
ok = self._rtde_c.moveL(pose, vel, acc, asynchronous=asynchronous)
|
|
217
|
+
if ok is False:
|
|
218
|
+
raise MotionError(
|
|
219
|
+
"moveL rejected by the controller: the RTDE control script "
|
|
220
|
+
"stopped, the robot entered a stop state, or the command "
|
|
221
|
+
"timed out. Set URKIT_RTDE_STDERR=<file> to capture "
|
|
222
|
+
"ur_rtde's error output."
|
|
223
|
+
)
|
|
201
224
|
except MotionError:
|
|
202
225
|
raise
|
|
203
226
|
except Exception as e:
|
|
@@ -249,7 +272,14 @@ class Motion:
|
|
|
249
272
|
current, target, frame.name, vel, acc,
|
|
250
273
|
)
|
|
251
274
|
with _suppress_rtde_stderr():
|
|
252
|
-
self._rtde_c.moveL(target, vel, acc)
|
|
275
|
+
ok = self._rtde_c.moveL(target, vel, acc)
|
|
276
|
+
if ok is False:
|
|
277
|
+
raise MotionError(
|
|
278
|
+
"moveL rejected by the controller: the RTDE control script "
|
|
279
|
+
"stopped, the robot entered a stop state, or the command "
|
|
280
|
+
"timed out. Set URKIT_RTDE_STDERR=<file> to capture "
|
|
281
|
+
"ur_rtde's error output."
|
|
282
|
+
)
|
|
253
283
|
except MotionError:
|
|
254
284
|
raise
|
|
255
285
|
except Exception as e:
|
|
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
|
|
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
|