v 2.1
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 2.3 MiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -73,6 +73,9 @@ SHEAR_FILTER_ALPHA = 0.65
|
|||||||
TRIGGER_NORMAL_FORCE = 0.1 # 单侧法向力触发阈值,单位 N。
|
TRIGGER_NORMAL_FORCE = 0.1 # 单侧法向力触发阈值,单位 N。
|
||||||
TRIGGER_SHEAR_FORCE = 0.1 # 单侧切向力触发阈值,单位 N。
|
TRIGGER_SHEAR_FORCE = 0.1 # 单侧切向力触发阈值,单位 N。
|
||||||
TRIGGER_CLEAR_STABLE_SECONDS = 0.2 # 张开恢复后,必须先看到触发力清掉并稳定这么久,才允许下一次扫过触发。
|
TRIGGER_CLEAR_STABLE_SECONDS = 0.2 # 张开恢复后,必须先看到触发力清掉并稳定这么久,才允许下一次扫过触发。
|
||||||
|
OPEN_WAIT_STABLE_SAMPLES = 12 # 张开等待时,用多少个近期样本判断当前稳定基准。
|
||||||
|
OPEN_WAIT_STABLE_RANGE_N = 0.03 # 近期样本最大-最小值小于该值,认为该通道趋于稳定,单位 N。
|
||||||
|
OPEN_WAIT_STABLE_TREND_N = 0.015 # 近期样本首尾变化小于该值,认为该通道无明显漂移,单位 N。
|
||||||
|
|
||||||
|
|
||||||
# 闭合夹取:物体移动到中间后,左右两边都超过该阈值就认为夹住。
|
# 闭合夹取:物体移动到中间后,左右两边都超过该阈值就认为夹住。
|
||||||
@@ -202,6 +205,9 @@ CONFIG = {
|
|||||||
"trigger_normal_force": TRIGGER_NORMAL_FORCE,
|
"trigger_normal_force": TRIGGER_NORMAL_FORCE,
|
||||||
"trigger_shear_force": TRIGGER_SHEAR_FORCE,
|
"trigger_shear_force": TRIGGER_SHEAR_FORCE,
|
||||||
"trigger_clear_stable_seconds": TRIGGER_CLEAR_STABLE_SECONDS,
|
"trigger_clear_stable_seconds": TRIGGER_CLEAR_STABLE_SECONDS,
|
||||||
|
"open_wait_stable_samples": OPEN_WAIT_STABLE_SAMPLES,
|
||||||
|
"open_wait_stable_range_n": OPEN_WAIT_STABLE_RANGE_N,
|
||||||
|
"open_wait_stable_trend_n": OPEN_WAIT_STABLE_TREND_N,
|
||||||
"grip_normal_force": GRIP_NORMAL_FORCE,
|
"grip_normal_force": GRIP_NORMAL_FORCE,
|
||||||
"grip_requires_both": GRIP_REQUIRES_BOTH,
|
"grip_requires_both": GRIP_REQUIRES_BOTH,
|
||||||
"close_timeout_seconds": CLOSE_TIMEOUT_SECONDS,
|
"close_timeout_seconds": CLOSE_TIMEOUT_SECONDS,
|
||||||
|
|||||||
@@ -127,12 +127,19 @@ class SideTriggerGripController:
|
|||||||
trigger_clear_since = None
|
trigger_clear_since = None
|
||||||
trigger_armed = False
|
trigger_armed = False
|
||||||
last_trigger = False
|
last_trigger = False
|
||||||
|
open_wait_samples = []
|
||||||
|
open_wait_stable_values = {}
|
||||||
force_pct = int(clamp(config.open_force, config.force_min, config.force_max))
|
force_pct = int(clamp(config.open_force, config.force_min, config.force_max))
|
||||||
adaptive_target_force = int(clamp(config.hold_force, config.force_min, config.force_max))
|
adaptive_target_force = int(clamp(config.hold_force, config.force_min, config.force_max))
|
||||||
release_armed = False
|
release_armed = False
|
||||||
hold_pos = None
|
hold_pos = None
|
||||||
reference = None
|
reference = None
|
||||||
|
|
||||||
|
def reset_open_wait_stability():
|
||||||
|
nonlocal open_wait_samples, open_wait_stable_values
|
||||||
|
open_wait_samples = []
|
||||||
|
open_wait_stable_values = {}
|
||||||
|
|
||||||
while not self._stop_event.is_set():
|
while not self._stop_event.is_set():
|
||||||
tick_start = time.perf_counter()
|
tick_start = time.perf_counter()
|
||||||
now = time.perf_counter()
|
now = time.perf_counter()
|
||||||
@@ -156,6 +163,7 @@ class SideTriggerGripController:
|
|||||||
recover_position_since = None
|
recover_position_since = None
|
||||||
trigger_clear_since = None
|
trigger_clear_since = None
|
||||||
trigger_armed = False
|
trigger_armed = False
|
||||||
|
reset_open_wait_stability()
|
||||||
release_armed = False
|
release_armed = False
|
||||||
hold_pos = None
|
hold_pos = None
|
||||||
reference = None
|
reference = None
|
||||||
@@ -177,6 +185,7 @@ class SideTriggerGripController:
|
|||||||
recover_position_since = None
|
recover_position_since = None
|
||||||
trigger_clear_since = None
|
trigger_clear_since = None
|
||||||
trigger_armed = False
|
trigger_armed = False
|
||||||
|
reset_open_wait_stability()
|
||||||
release_armed = False
|
release_armed = False
|
||||||
hold_pos = None
|
hold_pos = None
|
||||||
reference = None
|
reference = None
|
||||||
@@ -194,6 +203,7 @@ class SideTriggerGripController:
|
|||||||
last_force_ramp_time = now
|
last_force_ramp_time = now
|
||||||
trigger_clear_since = None
|
trigger_clear_since = None
|
||||||
trigger_armed = False
|
trigger_armed = False
|
||||||
|
reset_open_wait_stability()
|
||||||
release_armed = False
|
release_armed = False
|
||||||
reference = None
|
reference = None
|
||||||
manual_action = "manual-hold"
|
manual_action = "manual-hold"
|
||||||
@@ -203,6 +213,7 @@ class SideTriggerGripController:
|
|||||||
state_start = now
|
state_start = now
|
||||||
trigger_clear_since = None
|
trigger_clear_since = None
|
||||||
trigger_armed = False
|
trigger_armed = False
|
||||||
|
reset_open_wait_stability()
|
||||||
release_armed = False
|
release_armed = False
|
||||||
hold_pos = None
|
hold_pos = None
|
||||||
reference = None
|
reference = None
|
||||||
@@ -239,21 +250,21 @@ class SideTriggerGripController:
|
|||||||
if manual_action is not None:
|
if manual_action is not None:
|
||||||
pass
|
pass
|
||||||
elif state == "open_wait":
|
elif state == "open_wait":
|
||||||
trigger_edge = trigger and not last_trigger
|
open_wait_sample = self._open_wait_sample(feedback)
|
||||||
|
self._append_open_wait_sample(open_wait_samples, open_wait_sample)
|
||||||
|
stable_values = self._open_wait_stable_values(open_wait_samples)
|
||||||
|
trigger = self._open_wait_delta_triggered(
|
||||||
|
open_wait_sample,
|
||||||
|
open_wait_stable_values,
|
||||||
|
)
|
||||||
if not trigger:
|
if not trigger:
|
||||||
if trigger_clear_since is None:
|
open_wait_stable_values.update(stable_values)
|
||||||
trigger_clear_since = now
|
|
||||||
action = "open-wait-trigger-clearing"
|
trigger_armed = bool(open_wait_stable_values)
|
||||||
elif now - trigger_clear_since >= config.trigger_clear_stable_seconds:
|
if not trigger_armed:
|
||||||
trigger_armed = True
|
action = "open-wait-stabilizing"
|
||||||
|
elif not trigger:
|
||||||
action = "open-wait-armed"
|
action = "open-wait-armed"
|
||||||
else:
|
|
||||||
action = "open-wait-trigger-clearing"
|
|
||||||
elif not trigger_armed:
|
|
||||||
trigger_clear_since = None
|
|
||||||
action = "open-wait-clear-trigger"
|
|
||||||
elif not trigger_edge:
|
|
||||||
action = "open-wait-trigger-held"
|
|
||||||
else:
|
else:
|
||||||
force_pct = int(clamp(
|
force_pct = int(clamp(
|
||||||
config.close_force,
|
config.close_force,
|
||||||
@@ -271,6 +282,7 @@ class SideTriggerGripController:
|
|||||||
recover_position_since = None
|
recover_position_since = None
|
||||||
trigger_clear_since = None
|
trigger_clear_since = None
|
||||||
trigger_armed = False
|
trigger_armed = False
|
||||||
|
reset_open_wait_stability()
|
||||||
adaptive_target_force = self._adaptive_force_target(feedback)
|
adaptive_target_force = self._adaptive_force_target(feedback)
|
||||||
release_armed = False
|
release_armed = False
|
||||||
hold_pos = None
|
hold_pos = None
|
||||||
@@ -308,6 +320,7 @@ class SideTriggerGripController:
|
|||||||
last_open_position_ready = False
|
last_open_position_ready = False
|
||||||
recover_quiet_since = None
|
recover_quiet_since = None
|
||||||
recover_position_since = None
|
recover_position_since = None
|
||||||
|
reset_open_wait_stability()
|
||||||
release_armed = False
|
release_armed = False
|
||||||
hold_pos = None
|
hold_pos = None
|
||||||
reference = None
|
reference = None
|
||||||
@@ -404,6 +417,7 @@ class SideTriggerGripController:
|
|||||||
last_open_position_ready = False
|
last_open_position_ready = False
|
||||||
recover_quiet_since = None
|
recover_quiet_since = None
|
||||||
recover_position_since = None
|
recover_position_since = None
|
||||||
|
reset_open_wait_stability()
|
||||||
release_armed = False
|
release_armed = False
|
||||||
hold_pos = None
|
hold_pos = None
|
||||||
reference = None
|
reference = None
|
||||||
@@ -442,6 +456,7 @@ class SideTriggerGripController:
|
|||||||
recover_position_since = None
|
recover_position_since = None
|
||||||
trigger_clear_since = None
|
trigger_clear_since = None
|
||||||
trigger_armed = True
|
trigger_armed = True
|
||||||
|
reset_open_wait_stability()
|
||||||
last_trigger = trigger
|
last_trigger = trigger
|
||||||
release_armed = False
|
release_armed = False
|
||||||
hold_pos = None
|
hold_pos = None
|
||||||
@@ -464,6 +479,7 @@ class SideTriggerGripController:
|
|||||||
recover_position_since = None
|
recover_position_since = None
|
||||||
trigger_clear_since = None
|
trigger_clear_since = None
|
||||||
trigger_armed = True
|
trigger_armed = True
|
||||||
|
reset_open_wait_stability()
|
||||||
last_trigger = trigger
|
last_trigger = trigger
|
||||||
release_armed = False
|
release_armed = False
|
||||||
hold_pos = None
|
hold_pos = None
|
||||||
@@ -526,6 +542,51 @@ class SideTriggerGripController:
|
|||||||
or feedback.max_shear >= self.config.trigger_shear_force
|
or feedback.max_shear >= self.config.trigger_shear_force
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _open_wait_sample(self, feedback):
|
||||||
|
return {
|
||||||
|
"left_normal": feedback.left_normal,
|
||||||
|
"right_normal": feedback.right_normal,
|
||||||
|
"left_shear": feedback.left_shear,
|
||||||
|
"right_shear": feedback.right_shear,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _append_open_wait_sample(self, samples, sample):
|
||||||
|
samples.append(sample)
|
||||||
|
stable_samples = max(2, int(self.config.open_wait_stable_samples))
|
||||||
|
if len(samples) > stable_samples:
|
||||||
|
del samples[0]
|
||||||
|
|
||||||
|
def _open_wait_stable_values(self, samples):
|
||||||
|
stable_samples = max(2, int(self.config.open_wait_stable_samples))
|
||||||
|
if len(samples) < stable_samples:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
stable = {}
|
||||||
|
first = samples[0]
|
||||||
|
last = samples[-1]
|
||||||
|
for key in first:
|
||||||
|
values = [sample[key] for sample in samples]
|
||||||
|
value_range = max(values) - min(values)
|
||||||
|
trend = abs(last[key] - first[key])
|
||||||
|
if (
|
||||||
|
value_range <= self.config.open_wait_stable_range_n
|
||||||
|
and trend <= self.config.open_wait_stable_trend_n
|
||||||
|
):
|
||||||
|
stable[key] = sum(values) / len(values)
|
||||||
|
return stable
|
||||||
|
|
||||||
|
def _open_wait_delta_triggered(self, sample, stable_values):
|
||||||
|
thresholds = {
|
||||||
|
"left_normal": self.config.trigger_normal_force,
|
||||||
|
"right_normal": self.config.trigger_normal_force,
|
||||||
|
"left_shear": self.config.trigger_shear_force,
|
||||||
|
"right_shear": self.config.trigger_shear_force,
|
||||||
|
}
|
||||||
|
for key, stable_value in stable_values.items():
|
||||||
|
if sample[key] - stable_value >= thresholds[key]:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def _grip_contact(self, feedback):
|
def _grip_contact(self, feedback):
|
||||||
left_contact = feedback.left_normal >= self.config.grip_normal_force
|
left_contact = feedback.left_normal >= self.config.grip_normal_force
|
||||||
right_contact = feedback.right_normal >= self.config.grip_normal_force
|
right_contact = feedback.right_normal >= self.config.grip_normal_force
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ class Demo02Config:
|
|||||||
trigger_normal_force: float = 0.04
|
trigger_normal_force: float = 0.04
|
||||||
trigger_shear_force: float = 0.04
|
trigger_shear_force: float = 0.04
|
||||||
trigger_clear_stable_seconds: float = 0.2
|
trigger_clear_stable_seconds: float = 0.2
|
||||||
|
open_wait_stable_samples: int = 12
|
||||||
|
open_wait_stable_range_n: float = 0.03
|
||||||
|
open_wait_stable_trend_n: float = 0.015
|
||||||
grip_normal_force: float = 0.05
|
grip_normal_force: float = 0.05
|
||||||
grip_requires_both: bool = True
|
grip_requires_both: bool = True
|
||||||
close_timeout_seconds: float = 5.0
|
close_timeout_seconds: float = 5.0
|
||||||
@@ -151,6 +154,9 @@ class Demo02Config:
|
|||||||
add("--trigger-normal-force", type=float, default=get("trigger_normal_force", cls.trigger_normal_force))
|
add("--trigger-normal-force", type=float, default=get("trigger_normal_force", cls.trigger_normal_force))
|
||||||
add("--trigger-shear-force", type=float, default=get("trigger_shear_force", cls.trigger_shear_force))
|
add("--trigger-shear-force", type=float, default=get("trigger_shear_force", cls.trigger_shear_force))
|
||||||
add("--trigger-clear-stable-seconds", type=float, default=get("trigger_clear_stable_seconds", cls.trigger_clear_stable_seconds))
|
add("--trigger-clear-stable-seconds", type=float, default=get("trigger_clear_stable_seconds", cls.trigger_clear_stable_seconds))
|
||||||
|
add("--open-wait-stable-samples", type=int, default=get("open_wait_stable_samples", cls.open_wait_stable_samples))
|
||||||
|
add("--open-wait-stable-range-n", type=float, default=get("open_wait_stable_range_n", cls.open_wait_stable_range_n))
|
||||||
|
add("--open-wait-stable-trend-n", type=float, default=get("open_wait_stable_trend_n", cls.open_wait_stable_trend_n))
|
||||||
add("--grip-normal-force", type=float, default=get("grip_normal_force", cls.grip_normal_force))
|
add("--grip-normal-force", type=float, default=get("grip_normal_force", cls.grip_normal_force))
|
||||||
add("--grip-requires-both", dest="grip_requires_both", action="store_true", default=get("grip_requires_both", cls.grip_requires_both))
|
add("--grip-requires-both", dest="grip_requires_both", action="store_true", default=get("grip_requires_both", cls.grip_requires_both))
|
||||||
add("--allow-single-side-grip", dest="grip_requires_both", action="store_false")
|
add("--allow-single-side-grip", dest="grip_requires_both", action="store_false")
|
||||||
|
|||||||
@@ -97,6 +97,62 @@ def fmt(value, unit="", width=0, precision=3):
|
|||||||
return f"{text}{unit}"
|
return f"{text}{unit}"
|
||||||
|
|
||||||
|
|
||||||
|
STATE_TEXT = {
|
||||||
|
"open_wait": "等待触发",
|
||||||
|
"closing": "闭合中",
|
||||||
|
"gripping": "夹持加力",
|
||||||
|
"hold_check": "夹持检测",
|
||||||
|
"open_recover": "张开恢复",
|
||||||
|
"manual_close": "手动闭合",
|
||||||
|
"manual_hold": "手动保持",
|
||||||
|
"error": "错误",
|
||||||
|
}
|
||||||
|
|
||||||
|
ACTION_TEXT = {
|
||||||
|
"wait": "等待",
|
||||||
|
"waiting-for-sensor-samples": "等待传感器数据",
|
||||||
|
"open-wait-stabilizing": "等待稳定基准",
|
||||||
|
"open-wait-armed": "已就绪",
|
||||||
|
"side-trigger-close": "侧边触发闭合",
|
||||||
|
"closing-to-grip": "闭合寻找物体",
|
||||||
|
"object-detected-low-force": "检测到物体",
|
||||||
|
"close-timeout-open": "闭合超时张开",
|
||||||
|
"grip-ramp-up": "夹持加力",
|
||||||
|
"grip-hold": "夹持保持",
|
||||||
|
"hold-reference-armed": "记录夹持基准",
|
||||||
|
"adaptive-grip-ramp": "自适应加力",
|
||||||
|
"adaptive-grip-wait": "等待自适应加力",
|
||||||
|
"release-arm-delay": "释放检测延时",
|
||||||
|
"release-reference-armed": "释放检测就绪",
|
||||||
|
"hold-check": "保持检测",
|
||||||
|
"force-change-release-open": "力变化张开",
|
||||||
|
"open-recover-position": "张开位置恢复",
|
||||||
|
"open-recover-position-stabilizing": "张开位置稳定中",
|
||||||
|
"open-ready-position": "张开到位",
|
||||||
|
"open-recover-quiet": "力稳定恢复",
|
||||||
|
"open-recover-stabilizing": "恢复稳定中",
|
||||||
|
"open-recover-wait-force-clear": "等待力恢复",
|
||||||
|
"open-ready": "恢复完成",
|
||||||
|
"manual-open": "手动张开",
|
||||||
|
"manual-close": "手动闭合",
|
||||||
|
"manual-hold": "手动保持",
|
||||||
|
"manual-release": "手动释放",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def label_with_cn(value, mapping):
|
||||||
|
value = str(value or "--")
|
||||||
|
cn = mapping.get(value)
|
||||||
|
if cn is None:
|
||||||
|
for prefix, text in mapping.items():
|
||||||
|
if value.startswith(f"{prefix} "):
|
||||||
|
cn = text
|
||||||
|
break
|
||||||
|
if cn is None:
|
||||||
|
return value
|
||||||
|
return f"{value}({cn})"
|
||||||
|
|
||||||
|
|
||||||
STYLE = """
|
STYLE = """
|
||||||
QMainWindow {
|
QMainWindow {
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
@@ -140,6 +196,22 @@ QLabel#StatusChip {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
}
|
}
|
||||||
|
QFrame#StatusPanel {
|
||||||
|
background-color: #101010;
|
||||||
|
border: 1px solid #303030;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
QLabel#StatusMain {
|
||||||
|
color: #00e5ff;
|
||||||
|
font-family: "Cascadia Code", "Consolas";
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
QLabel#StatusSub {
|
||||||
|
color: #9a9a9a;
|
||||||
|
font-family: "Cascadia Code", "Consolas";
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
QLabel#ActionChip {
|
QLabel#ActionChip {
|
||||||
background-color: #0b0b0b;
|
background-color: #0b0b0b;
|
||||||
border: 1px solid #262626;
|
border: 1px solid #262626;
|
||||||
@@ -332,8 +404,17 @@ class GripperDemo02Viewer(QMainWindow):
|
|||||||
top.setSpacing(8)
|
top.setSpacing(8)
|
||||||
title = QLabel("Orisys 夹爪")
|
title = QLabel("Orisys 夹爪")
|
||||||
title.setObjectName("Title")
|
title.setObjectName("Title")
|
||||||
|
self.status_panel = QFrame()
|
||||||
|
self.status_panel.setObjectName("StatusPanel")
|
||||||
|
status_layout = QVBoxLayout(self.status_panel)
|
||||||
|
status_layout.setContentsMargins(10, 6, 10, 6)
|
||||||
|
status_layout.setSpacing(1)
|
||||||
self.status_chip = QLabel("state=--")
|
self.status_chip = QLabel("state=--")
|
||||||
self.status_chip.setObjectName("StatusChip")
|
self.status_chip.setObjectName("StatusMain")
|
||||||
|
self.action_label = QLabel("action=--")
|
||||||
|
self.action_label.setObjectName("StatusSub")
|
||||||
|
status_layout.addWidget(self.status_chip)
|
||||||
|
status_layout.addWidget(self.action_label)
|
||||||
self.start_button = AnimatedButton("启动")
|
self.start_button = AnimatedButton("启动")
|
||||||
self.start_button.setObjectName("StartButton")
|
self.start_button.setObjectName("StartButton")
|
||||||
self.start_button.clicked.connect(self.start_control)
|
self.start_button.clicked.connect(self.start_control)
|
||||||
@@ -355,7 +436,7 @@ class GripperDemo02Viewer(QMainWindow):
|
|||||||
self.manual_hold_button.clicked.connect(self.toggle_manual_hold)
|
self.manual_hold_button.clicked.connect(self.toggle_manual_hold)
|
||||||
self._set_button_state("idle")
|
self._set_button_state("idle")
|
||||||
top.addWidget(title)
|
top.addWidget(title)
|
||||||
top.addWidget(self.status_chip)
|
top.addWidget(self.status_panel)
|
||||||
top.addStretch(1)
|
top.addStretch(1)
|
||||||
top.addWidget(self.start_button)
|
top.addWidget(self.start_button)
|
||||||
top.addWidget(self.stop_button)
|
top.addWidget(self.stop_button)
|
||||||
@@ -557,9 +638,11 @@ class GripperDemo02Viewer(QMainWindow):
|
|||||||
self._set_button_state("running")
|
self._set_button_state("running")
|
||||||
|
|
||||||
state = status.get("state", "--")
|
state = status.get("state", "--")
|
||||||
|
action = status.get("action", "--")
|
||||||
unit = status.get("normal_unit", "N")
|
unit = status.get("normal_unit", "N")
|
||||||
shear_unit = status.get("shear_unit", "N")
|
shear_unit = status.get("shear_unit", "N")
|
||||||
self.status_chip.setText(f"state={state}")
|
self.status_chip.setText(f"state={label_with_cn(state, STATE_TEXT)}")
|
||||||
|
self.action_label.setText(f"action={label_with_cn(action, ACTION_TEXT)}")
|
||||||
self.manual_hold_button.setText(
|
self.manual_hold_button.setText(
|
||||||
"释放" if bool(status.get("manual_hold_active")) else "保持"
|
"释放" if bool(status.get("manual_hold_active")) else "保持"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user