左手G20大拇指侧摆映射
This commit is contained in:
@@ -65,12 +65,12 @@ G20_COMMAND_NAMES: tuple[str, ...] = (
|
||||
FLEXION_MOTOR_INDICES = frozenset((*range(0, 5), *range(15, 20)))
|
||||
RESERVED_MOTOR_INDICES = frozenset(range(11, 15))
|
||||
|
||||
# Left-G20 endpoints from linkerhand_retarget/config/hand_config.yml. Motor 5
|
||||
# is fixed at zero by the vendor mapping; motors 6..9 run in the opposite
|
||||
# Left-G20 endpoints from linkerhand_retarget/config/hand_config.yml. Motor 5
|
||||
# follows thumb roll like the right G20; motors 6..9 run in the opposite
|
||||
# direction to the flexion motors on a left hand.
|
||||
LOWER_LIMIT_COMMAND: tuple[int, ...] = (
|
||||
255, 255, 255, 255, 255,
|
||||
0, 0, 0, 0, 0,
|
||||
255, 0, 0, 0, 0,
|
||||
255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255,
|
||||
)
|
||||
@@ -118,8 +118,9 @@ POWER_GRASP_THUMB_OPPOSITION_COMMANDS = np.asarray(
|
||||
(78.0, 112.0), dtype=float
|
||||
)
|
||||
|
||||
# The vendor G20 mapping constrains thumb horizontal swing (motor 10) to this
|
||||
# range to avoid commanding the mechanical end stop.
|
||||
# Physically verified left-G20 limits keep both thumb side-motion channels
|
||||
# away from their mechanical end stops.
|
||||
THUMB_ABDUCTION_MIN_COMMAND = 20.0
|
||||
THUMB_YAW_MIN_COMMAND = 80.0
|
||||
|
||||
# MediaPipe landmark chains for thumb, index, middle, ring and pinky.
|
||||
@@ -294,6 +295,7 @@ class G20CommandMapper:
|
||||
command[motor_index] = max(
|
||||
command[motor_index], minimum_flexion_command
|
||||
)
|
||||
command[5] = max(command[5], THUMB_ABDUCTION_MIN_COMMAND)
|
||||
command[10] = max(command[10], THUMB_YAW_MIN_COMMAND)
|
||||
command[list(RESERVED_MOTOR_INDICES)] = self.open_pose[
|
||||
list(RESERVED_MOTOR_INDICES)
|
||||
|
||||
@@ -6,6 +6,7 @@ from camera_retarget_l20.g20_mapping import (
|
||||
FLEXION_MOTOR_INDICES,
|
||||
MOTOR_JOINT_NAMES,
|
||||
RESERVED_MOTOR_INDICES,
|
||||
THUMB_ABDUCTION_MIN_COMMAND,
|
||||
THUMB_YAW_MIN_COMMAND,
|
||||
G20CommandMapper,
|
||||
G20MediaPipeMapper,
|
||||
@@ -52,7 +53,7 @@ def qpos_at(limit_index):
|
||||
def test_left_g20_lower_limit_endpoints():
|
||||
command = make_mapper().raw_command(qpos_at(0))
|
||||
assert command[:5].tolist() == [255.0] * 5
|
||||
assert command[5:10].tolist() == [0.0] * 5
|
||||
assert command[5:10].tolist() == [255.0, 0.0, 0.0, 0.0, 0.0]
|
||||
assert command[10] == 255.0
|
||||
assert command[15:].tolist() == [255.0] * 5
|
||||
assert command[list(RESERVED_MOTOR_INDICES)].tolist() == [255.0] * 4
|
||||
@@ -61,7 +62,7 @@ def test_left_g20_lower_limit_endpoints():
|
||||
def test_left_g20_upper_limit_endpoints_and_thumb_constraint():
|
||||
command = make_mapper().raw_command(qpos_at(1))
|
||||
assert command[:5].tolist() == [0.0] * 5
|
||||
assert command[5] == 0.0
|
||||
assert command[5] == THUMB_ABDUCTION_MIN_COMMAND
|
||||
assert command[6:10].tolist() == [255.0] * 4
|
||||
assert command[10] == THUMB_YAW_MIN_COMMAND
|
||||
assert command[15:].tolist() == [0.0] * 5
|
||||
@@ -71,7 +72,7 @@ def test_max_closure_only_limits_flexion_motors():
|
||||
command = make_mapper(max_closure=0.60).raw_command(qpos_at(1))
|
||||
for index in FLEXION_MOTOR_INDICES:
|
||||
assert command[index] == pytest.approx(102.0)
|
||||
assert command[5] == 0.0
|
||||
assert command[5] == THUMB_ABDUCTION_MIN_COMMAND
|
||||
assert command[6:10].tolist() == [255.0] * 4
|
||||
assert command[10] == THUMB_YAW_MIN_COMMAND
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ serial:
|
||||
- 921600
|
||||
exclude_ports: []
|
||||
left:
|
||||
baudrate: 460800
|
||||
port: /dev/ttyUSB1
|
||||
baudrate: 2000000
|
||||
port: /dev/ttyUSB0
|
||||
right:
|
||||
baudrate: 2000000
|
||||
port: /dev/ttyUSB0
|
||||
@@ -41,7 +41,7 @@ system:
|
||||
motion_type: linkerforce
|
||||
retargeting_type: projection
|
||||
rightpub: true
|
||||
robotname_l: l10
|
||||
robotname_l: g20
|
||||
robotname_r: l10
|
||||
sapientype: left
|
||||
usecan: false
|
||||
|
||||
@@ -16,7 +16,7 @@ commandlower_left_l20: [255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, None, Non
|
||||
commandupper_left_l20: [0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, None, None, None, None, 0, 0, 0, 0, 0]
|
||||
commandlower_right_g20: [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, None, None, None, None, 255, 255, 255, 255, 255]
|
||||
commandupper_right_g20: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, None, None, None, None, 0, 0, 0, 0, 0]
|
||||
commandlower_left_g20: [255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 255, None, None, None, None, 255, 255, 255, 255, 255]
|
||||
commandlower_left_g20: [255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, None, None, None, None, 255, 255, 255, 255, 255]
|
||||
commandupper_left_g20: [0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, None, None, None, None, 0, 0, 0, 0, 0]
|
||||
commandlower_right_l7: [255, 255, 255, 255, 255, 255, 255, None, None, None, None, None, None, None, None, None, None, None, None, None]
|
||||
commandupper_right_l7: [0, 0, 0, 0, 0, 0, 0, None, None, None, None, None, None, None, None, None, None, None, None, None]
|
||||
|
||||
@@ -432,7 +432,9 @@ MOTOR_CONSTRAINTS = {
|
||||
{'min': 0, 'max': 255, 'enabled': False},
|
||||
{'min': 0, 'max': 255, 'enabled': False},
|
||||
{'min': 0, 'max': 255, 'enabled': False},
|
||||
{'min': 0, 'max': 255, 'enabled': False},
|
||||
# Left G20 thumb abduction is enabled by hand_config.yml. Keep the
|
||||
# physically verified command away from the mechanical end stop.
|
||||
{'min': 20, 'max': 255, 'enabled': True},
|
||||
{'min': 0, 'max': 255, 'enabled': False},
|
||||
{'min': 0, 'max': 255, 'enabled': False},
|
||||
{'min': 0, 'max': 255, 'enabled': False},
|
||||
@@ -470,4 +472,4 @@ MOTOR_CONSTRAINTS = {
|
||||
{'min': 0, 'max': 255, 'enabled': False},
|
||||
{'min': 0, 'max': 255, 'enabled': False},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+14
-1
@@ -10,6 +10,13 @@ from ..config.g20_config import FINGER_CONFIGS, MAPPING_ORDER, ROBOT_OPOSE_RIGHT
|
||||
from typing import List
|
||||
from linkerhand.handcoreex import DynamicWeightMultiStateLinearMapper,MultiStateLinearMapper
|
||||
|
||||
|
||||
# The left G20 ring abduction motor runs opposite to the glove's corresponding
|
||||
# roll sensor. Keep this override local to LeftHand so the pinky and right-hand
|
||||
# mappings retain their native directions.
|
||||
LEFT_REVERSED_SIDE_SWINGS = ('ring_roll',)
|
||||
|
||||
|
||||
def _resolve_version_config(configs: dict, version: str) -> dict:
|
||||
"""
|
||||
解析版本配置,将字典格式的 weights/reverse_motion 转换为具体值
|
||||
@@ -300,6 +307,7 @@ class LeftHand:
|
||||
|
||||
finger_configs = _resolve_version_config(FINGER_CONFIGS, self.glove_version)
|
||||
self.multi_state_mapper = DynamicWeightMultiStateLinearMapper(finger_configs, MAPPING_ORDER, is_debug=is_debug)
|
||||
self._apply_side_direction_overrides()
|
||||
|
||||
for config_name, config in FINGER_CONFIGS.items():
|
||||
if config.get('dynamic_weight'):
|
||||
@@ -307,6 +315,10 @@ class LeftHand:
|
||||
|
||||
self.motor_constraints = MOTOR_CONSTRAINTS['left']
|
||||
|
||||
def _apply_side_direction_overrides(self):
|
||||
for finger_name in LEFT_REVERSED_SIDE_SWINGS:
|
||||
self.multi_state_mapper.finger_configs[finger_name]['reverse_motion'] = True
|
||||
|
||||
def _apply_motor_constraints(self):
|
||||
for i, constraint in enumerate(self.motor_constraints):
|
||||
if constraint.get('enabled', False):
|
||||
@@ -335,6 +347,8 @@ class LeftHand:
|
||||
if version_key in config['reverse_motion']:
|
||||
self.multi_state_mapper.finger_configs[finger_name]['reverse_motion'] = config['reverse_motion'][version_key]
|
||||
|
||||
self._apply_side_direction_overrides()
|
||||
|
||||
def initialize_mapper(self) -> bool:
|
||||
"""
|
||||
初始化映射器
|
||||
@@ -518,4 +532,3 @@ class LeftHand:
|
||||
self.g_jointvelocity[i] = 255
|
||||
self.last_jointvelocity[i] = self.g_jointvelocity[i]
|
||||
self.last_jointpositions[i] = self.g_jointpositions[i]
|
||||
|
||||
|
||||
@@ -1247,7 +1247,7 @@ class Retarget():
|
||||
self.node.get_logger().error("标定失败,退出程序")
|
||||
return
|
||||
self.calibration = -1
|
||||
self.node.create_timer(1.0/100, self.process_callback) # 60Hz
|
||||
self.node.create_timer(1.0/100, self.process_callback) # 100Hz
|
||||
|
||||
def stop_serial_threads(self):
|
||||
"""停止串口线程,在 destroy_node 时调用"""
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
from linkerhand_retarget.motion.linkerforce.hand.linkerforce_g20 import (
|
||||
LeftHand,
|
||||
RightHand,
|
||||
)
|
||||
from linkerhand_retarget.motion.linkerforce.config.g20_config import MOTOR_CONSTRAINTS
|
||||
|
||||
|
||||
class _HandCoreStub:
|
||||
pass
|
||||
|
||||
|
||||
def test_left_g20_thumb_side_swing_has_a_safe_command_range():
|
||||
package_dir = Path(__file__).resolve().parents[2] / 'linkerhand_retarget'
|
||||
config = yaml.safe_load((package_dir / 'config' / 'hand_config.yml').read_text())
|
||||
|
||||
assert config['commandlower_left_g20'][5] == 255
|
||||
assert config['commandupper_left_g20'][5] == 0
|
||||
assert MOTOR_CONSTRAINTS['left'][5] == {
|
||||
'min': 20,
|
||||
'max': 255,
|
||||
'enabled': True,
|
||||
}
|
||||
|
||||
|
||||
def test_left_g20_reverses_only_ring_side_swing():
|
||||
hand = LeftHand(_HandCoreStub())
|
||||
|
||||
assert hand.multi_state_mapper.finger_configs['index_roll']['reverse_motion'] is False
|
||||
assert hand.multi_state_mapper.finger_configs['middle_roll']['reverse_motion'] is False
|
||||
assert hand.multi_state_mapper.finger_configs['ring_roll']['reverse_motion'] is True
|
||||
assert hand.multi_state_mapper.finger_configs['pinky_roll']['reverse_motion'] is False
|
||||
|
||||
|
||||
def test_left_g20_side_direction_overrides_survive_version_change():
|
||||
hand = LeftHand(_HandCoreStub())
|
||||
|
||||
hand.set_glove_version('1.2.12')
|
||||
|
||||
assert hand.multi_state_mapper.finger_configs['ring_roll']['reverse_motion'] is True
|
||||
assert hand.multi_state_mapper.finger_configs['pinky_roll']['reverse_motion'] is False
|
||||
|
||||
|
||||
def test_right_g20_side_directions_are_unchanged():
|
||||
hand = RightHand(_HandCoreStub())
|
||||
|
||||
assert hand.multi_state_mapper.finger_configs['ring_roll']['reverse_motion'] is False
|
||||
assert hand.multi_state_mapper.finger_configs['pinky_roll']['reverse_motion'] is False
|
||||
Reference in New Issue
Block a user