Skip to content

Commit 683fa6a

Browse files
committed
bump version to 0.3.7
- Add zero_ft_sensor() to zero the robot's force/torque sensor - Add zero_first=True parameter to move_until_contact (zeros FT sensor by default) - Rename activate_gripper() to _activate_gripper() (private), use gripper.activate() as public API - Fix time import inconsistency in motion.py (was _time, now matches project style)
1 parent 1549e3b commit 683fa6a

7 files changed

Lines changed: 78 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.3.7] — 2026-07-07
9+
10+
### Added
11+
- `zero_ft_sensor()` — zero the robot's force/torque sensor (wraps `rtde_control.zeroFtSensor()`)
12+
- `move_until_contact(zero_first=True)` — new parameter to zero FT sensor before contact detection (default True)
13+
14+
### Changed
15+
- `activate_gripper()` renamed to `_activate_gripper()` (private) — use `robot.gripper.activate()` directly as the public API
16+
817
## [0.3.6] — 2026-07-07
918

1019
### Changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,17 @@ robot.move_sequence(["a", "b", "c"], blend_radius=0.02)
428428
#### Contact Detection
429429

430430
```python
431+
# Zeros FT sensor automatically, then moves until force exceeds threshold
431432
robot.move_until_contact([0, 0, -0.02, 0, 0, 0])
433+
434+
# Custom threshold (default: 5.0 N/Nm)
435+
robot.move_until_contact([0, 0, -0.02, 0, 0, 0], threshold=10.0)
436+
437+
# Skip zeroing if you need absolute force values
438+
robot.move_until_contact([0, 0, -0.02, 0, 0, 0], zero_first=False)
439+
440+
# Manual zero (e.g. before custom force-based logic)
441+
robot.zero_ft_sensor()
432442
```
433443

434444
#### Velocity Control

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "urkit"
7-
version = "0.3.6"
7+
version = "0.3.7"
88
description = "Universal Robots e-Series control toolkit built on ur_rtde"
99
readme = "README.md"
1010
license = {text = "MIT"}

src/urkit/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
from __future__ import annotations
2626

27-
__version__ = "0.3.6"
27+
__version__ = "0.3.7"
2828

2929
from urkit.config import load_config, resolve_config
3030
from urkit.exceptions import (

src/urkit/cli/teach.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1441,7 +1441,7 @@ def teach_command(args) -> None:
14411441
**gripper_kwargs,
14421442
)
14431443
print(" Connected.", flush=True)
1444-
if robot.activate_gripper():
1444+
if robot._activate_gripper():
14451445
print(" Gripper activated.", flush=True)
14461446
except ConnectionError as e:
14471447
print(f"Connection error: {e}")

src/urkit/motion.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import logging
1111
import os
1212
import sys
13-
import time as _time
13+
import time
1414
from contextlib import contextmanager
1515
from enum import IntEnum
1616
from typing import Iterator
@@ -255,12 +255,28 @@ def move_by(
255255
f"Relative move failed: {e}"
256256
)
257257

258+
def zero_ft_sensor(self) -> None:
259+
"""Zero the robot's force/torque sensor.
260+
261+
Clears the baseline so that subsequent ``getActualTCPForce()``
262+
readings reflect only forces applied after this call. Call before
263+
``move_until_contact()`` or any force-based operation.
264+
265+
Raises:
266+
MotionError: If the command fails.
267+
"""
268+
try:
269+
self._rtde_c.zeroFtSensor()
270+
except Exception as e:
271+
raise MotionError(f"Failed to zero FT sensor: {e}")
272+
258273
def move_until_contact(
259274
self,
260275
speed_vector: list[float],
261276
*,
262277
threshold: float = 5.0,
263278
acceleration: float = 0.1,
279+
zero_first: bool = True,
264280
) -> None:
265281
"""Move until contact is detected via TCP force sensing.
266282
@@ -280,12 +296,15 @@ def move_until_contact(
280296
Contact fires when any of the 6 wrench components changes
281297
by more than this value from the baseline reading.
282298
acceleration: Acceleration limit passed to ``speedL()`` in m/s².
299+
zero_first: If True (default), zero the FT sensor before reading
300+
the baseline. Set to False if you need absolute force values
301+
rather than delta from zero.
283302
284303
Raises:
285304
MotionError: If the command fails or the vector is invalid.
286305
287306
Example:
288-
>>> # Move straight down until contact
307+
>>> # Move straight down until contact (zeros FT sensor first)
289308
>>> motion.move_until_contact([0, 0, -0.02, 0, 0, 0])
290309
>>> # Move down while rotating, higher threshold
291310
>>> motion.move_until_contact([0, 0, -0.02, 0, 0.1, 0], threshold=10.0)
@@ -303,6 +322,11 @@ def move_until_contact(
303322
speed_vector, threshold,
304323
)
305324

325+
# Zero FT sensor to clear gravity bias before reading baseline
326+
if zero_first:
327+
self.zero_ft_sensor()
328+
time.sleep(0.05) # let sensor settle after zero
329+
306330
# Baseline force reading before the loop
307331
baseline = list(self._rtde_r.getActualTCPForce())
308332

@@ -387,9 +411,9 @@ def move_velocity(
387411
raise MotionError(f"Duration must be > 0, got {duration}.")
388412

389413
try:
390-
start = _time.monotonic()
414+
start = time.monotonic()
391415
while True:
392-
elapsed = _time.monotonic() - start
416+
elapsed = time.monotonic() - start
393417
if elapsed >= duration:
394418
break
395419
if not self._rtde_c.isConnected():

src/urkit/robot.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -254,19 +254,19 @@ def __init__(
254254

255255
logger.info("URRobot initialized at %s", ip)
256256

257-
def activate_gripper(self, *, timeout: float = 10.0) -> bool:
258-
"""Activate the gripper with a timeout.
257+
def _activate_gripper(self, *, timeout: float = 10.0) -> bool:
258+
"""Activate the gripper with error handling (internal use).
259259
260260
Tries to activate the configured gripper. If activation fails
261261
or times out (e.g., gripper not physically connected), disconnects
262262
the gripper, nulls out ``self._gripper``, and returns ``False``.
263263
264-
This is the single place that handles gripper activation — the
265-
CLI and library code should call this rather than touching
266-
``gripper.activate()`` directly.
264+
Users should call ``robot.gripper.activate()`` directly instead.
265+
This method exists for the CLI which needs graceful fallback
266+
when the gripper isn't connected.
267267
268268
Args:
269-
timeout: Maximum seconds to wait for activation (default 5.0).
269+
timeout: Maximum seconds to wait for activation (default 10.0).
270270
271271
Returns:
272272
``True`` if the gripper was activated successfully,
@@ -1113,16 +1113,31 @@ def move_sequence(
11131113
except Exception as e:
11141114
raise MotionError(f"move_sequence failed: {e}")
11151115

1116+
def zero_ft_sensor(self) -> None:
1117+
"""Zero the robot's force/torque sensor.
1118+
1119+
Clears the baseline so that subsequent force/torque readings
1120+
reflect only forces applied after this call. Call before
1121+
``move_until_contact()`` or any force-based operation.
1122+
1123+
Raises:
1124+
MotionError: If the command fails.
1125+
"""
1126+
self._check_connection()
1127+
self._motion.zero_ft_sensor()
1128+
11161129
def move_until_contact(
11171130
self,
11181131
speed_vector: list[float],
11191132
*,
11201133
threshold: float = 5.0,
11211134
acceleration: float = 0.1,
1135+
zero_first: bool = True,
11221136
) -> None:
11231137
"""Move until contact is detected via TCP force sensing.
11241138
11251139
Runs an interruptible control loop — press Ctrl+C to stop at any time.
1140+
Zeros the FT sensor by default before reading the baseline.
11261141
11271142
Args:
11281143
speed_vector: 6-element speed vector
@@ -1131,17 +1146,22 @@ def move_until_contact(
11311146
Contact fires when any wrench component changes by more
11321147
than this value from the baseline reading.
11331148
acceleration: Acceleration limit passed to ``speedL()`` in m/s².
1149+
zero_first: If True (default), zero the FT sensor before reading
1150+
the baseline. Set to False if you need absolute force values.
11341151
11351152
Example:
1136-
>>> # Move straight down until contact
1153+
>>> # Move straight down until contact (zeros FT sensor first)
11371154
>>> robot.move_until_contact([0, 0, -0.02, 0, 0, 0])
11381155
>>> # Higher threshold for heavier contact
11391156
>>> robot.move_until_contact([0, 0, -0.02, 0, 0, 0], threshold=10.0)
11401157
"""
11411158
self._check_connection()
11421159
self._disable_freedrive_guard()
11431160
self._motion.move_until_contact(
1144-
speed_vector, threshold=threshold, acceleration=acceleration
1161+
speed_vector,
1162+
threshold=threshold,
1163+
acceleration=acceleration,
1164+
zero_first=zero_first,
11451165
)
11461166

11471167
def move_velocity(

0 commit comments

Comments
 (0)