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.
Files changed (42) hide show
  1. {urkit-0.4.5 → urkit-0.4.6}/PKG-INFO +1 -1
  2. {urkit-0.4.5 → urkit-0.4.6}/pyproject.toml +1 -1
  3. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/__init__.py +1 -1
  4. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/cli/points.py +2 -0
  5. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/cli/teach.py +14 -0
  6. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/cli/terminal.py +44 -6
  7. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/motion.py +36 -6
  8. {urkit-0.4.5 → urkit-0.4.6}/src/urkit.egg-info/PKG-INFO +1 -1
  9. {urkit-0.4.5 → urkit-0.4.6}/README.md +0 -0
  10. {urkit-0.4.5 → urkit-0.4.6}/setup.cfg +0 -0
  11. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/__main__.py +0 -0
  12. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/cli/__init__.py +0 -0
  13. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/cli/colors.py +0 -0
  14. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/cli/connection_monitor.py +0 -0
  15. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/cli/init.py +0 -0
  16. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/config.py +0 -0
  17. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/connection.py +0 -0
  18. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/exceptions.py +0 -0
  19. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/geometry.py +0 -0
  20. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/gripper/__init__.py +0 -0
  21. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/gripper/base.py +0 -0
  22. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/gripper/digital.py +0 -0
  23. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/gripper/presets.py +0 -0
  24. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/gripper/robotiq.py +0 -0
  25. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/gripper/robotiq_preamble.py +0 -0
  26. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/io.py +0 -0
  27. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/points.py +0 -0
  28. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/robot.py +0 -0
  29. {urkit-0.4.5 → urkit-0.4.6}/src/urkit/telemetry.py +0 -0
  30. {urkit-0.4.5 → urkit-0.4.6}/src/urkit.egg-info/SOURCES.txt +0 -0
  31. {urkit-0.4.5 → urkit-0.4.6}/src/urkit.egg-info/dependency_links.txt +0 -0
  32. {urkit-0.4.5 → urkit-0.4.6}/src/urkit.egg-info/entry_points.txt +0 -0
  33. {urkit-0.4.5 → urkit-0.4.6}/src/urkit.egg-info/requires.txt +0 -0
  34. {urkit-0.4.5 → urkit-0.4.6}/src/urkit.egg-info/top_level.txt +0 -0
  35. {urkit-0.4.5 → urkit-0.4.6}/tests/test_exceptions.py +0 -0
  36. {urkit-0.4.5 → urkit-0.4.6}/tests/test_geometry.py +0 -0
  37. {urkit-0.4.5 → urkit-0.4.6}/tests/test_gripper.py +0 -0
  38. {urkit-0.4.5 → urkit-0.4.6}/tests/test_gripper_factory.py +0 -0
  39. {urkit-0.4.5 → urkit-0.4.6}/tests/test_gripper_presets.py +0 -0
  40. {urkit-0.4.5 → urkit-0.4.6}/tests/test_move_sequence.py +0 -0
  41. {urkit-0.4.5 → urkit-0.4.6}/tests/test_points.py +0 -0
  42. {urkit-0.4.5 → urkit-0.4.6}/tests/test_robot_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urkit
3
- Version: 0.4.5
3
+ Version: 0.4.6
4
4
  Summary: Universal Robots e-Series control toolkit built on ur_rtde
5
5
  Author: URKit Contributors
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "urkit"
7
- version = "0.4.5"
7
+ version = "0.4.6"
8
8
  description = "Universal Robots e-Series control toolkit built on ur_rtde"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -24,7 +24,7 @@ Quick start::
24
24
 
25
25
  from __future__ import annotations
26
26
 
27
- __version__ = "0.4.5"
27
+ __version__ = "0.4.6"
28
28
 
29
29
 
30
30
  from urkit.exceptions import (
@@ -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 True:
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
- if not msvcrt.kbhit():
180
- time.sleep(0.05)
181
- continue
182
- byte = msvcrt.getch()
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
- msvcrt.ungetch(byte[0])
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
- devnull = os.open(os.devnull, os.O_WRONLY)
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(devnull, 2)
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(devnull)
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urkit
3
- Version: 0.4.5
3
+ Version: 0.4.6
4
4
  Summary: Universal Robots e-Series control toolkit built on ur_rtde
5
5
  Author: URKit Contributors
6
6
  License: MIT
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