优化了副驾座椅控制

This commit is contained in:
minghanchen
2026-08-25 11:39:41 +08:00
parent 33c92d1352
commit 9ac8fdface
34 changed files with 31714 additions and 289 deletions
@@ -378,6 +378,9 @@ class HardwareSeatExecutor:
elif frame.arbitration_id == 0x4FA and len(frame.data) >= 8:
values = self.matrix.decode_by_frame_id(0x4FA, frame.data)
self._record_thermal_state(state, values, frame)
elif frame.arbitration_id == 0x306 and len(frame.data) >= 8:
values = self.matrix.decode_by_frame_id(0x306, frame.data)
self._record_dynamic_state(state, values, frame)
state["captured_at"] = _now()
state["source"] = "physical_can"
state["target"] = {
@@ -461,6 +464,30 @@ class HardwareSeatExecutor:
self._set_thermal_feature(state, feature, raw)
state[feature]["feedback_frame"] = frame.public()
@staticmethod
def _record_dynamic_state(
state: dict[str, Any],
values: dict[str, int],
frame: CanFrame,
) -> None:
mode_id = int(values["Dynamic_Mode_ID"])
dynamic_state = int(values["Dynamic_State"])
names = {1: "木马模式", 2: "摇篮模式"}
states = {0: "idle", 1: "entry", 2: "running", 3: "fault"}
state["dynamic_mode"] = {
"mode_id": mode_id,
"name": names.get(mode_id, ""),
"state": states.get(dynamic_state, "unknown"),
"phase": int(values["Dynamic_Phase"]),
"active_mask": int(values["Dynamic_Active_Mask"]),
"fault_mask": int(values["Dynamic_Fault_Mask"]),
"cycle_index": int(values["Dynamic_Cycle_Index"]),
"phase_elapsed_ms": int(values["Dynamic_Phase_Elapsed_Ms"]),
"repeat": "bounded_runtime" if dynamic_state in {1, 2} else "none",
"max_runtime_ms": 600_000,
"feedback_frame": frame.public(),
}
@staticmethod
def _set_thermal_feature(
state: dict[str, Any], feature: str, raw: int
+186 -12
View File
@@ -20,28 +20,55 @@ STATE_PATTERN = re.compile(
)
LEVEL_PATTERN = re.compile(r"(最高|最低|[1-5一二三四五高中低])\s*档")
POSITION_PATTERN_TEMPLATE = (
r"{alias}(?:位置)?(?:调到|移动到|移到|设为|设置为|调至|移动至|到|为)?"
r"{alias}(?:位置)?(?:调到|调至|移动到|移动至|移到|移至|"
r"转动到|转动至|旋转到|旋转至|调节到|调节至|调整到|调整至|"
r"设为|设置为|设置到|设置至|设置|设|到|为)?"
r"(?P<position>[+-]?\d{{1,3}}(?:\.\d+)?)%"
)
ANGLE_PATTERN_TEMPLATE = (
r"{alias}(?:夹角|角度)?(?:调到|放倒到|抬到|移动到|移到|设为|设置为|"
r"调至|到|为)?(?P<angle>\d{{2,3}})(?:度|°)"
r"{alias}(?:夹角|角度)?(?:调到|调至|放倒到|抬到|移动到|移动至|移到|移至|"
r"转动到|转动至|旋转到|旋转至|调节到|调节至|调整到|调整至|"
r"设为|设置为|设置到|设置至|设置|设|到|为)?"
r"(?P<angle>\d{{2,3}})(?:度|°)"
)
RELATIVE_ANGLE_PATTERN_TEMPLATE = (
r"{alias}(?:再|继续|接着|然后)?(?P<direction>向前|往前|朝前|前倾|向后|往后|朝后|后仰|"
r"{alias}(?:再|继续|接着|然后)?"
r"(?:移动|移|转动|旋转|转|调节|调整|调|设置|设|倾斜|抬|升|降)?"
r"(?:再|继续|接着|然后)?"
r"(?P<direction>向前|往前|朝前|前倾|向后|往后|朝后|后仰|"
r"向上|往上|抬高|抬起|升高|向下|往下|降低|降下|前|后|上|下)"
r"(?:移动|移|调节|调整|调|倾斜|抬|升|降)?(?P<delta>\d{{1,3}})(?:度|°)"
r"(?:移动|移|转动|旋转|转|调节|调整|调|设置|设|倾斜|抬|升|降)?"
r"(?P<delta>\d{{1,3}})(?:度|°)"
)
RELATIVE_PERCENT_PATTERN_TEMPLATE = (
r"{alias}(?:再|继续|接着|然后)?(?P<direction>向前|往前|朝前|前移|向后|往后|朝后|后移|"
r"{alias}(?:再|继续|接着|然后)?"
r"(?:移动|移|转动|旋转|转|调节|调整|调|设置|设|倾斜|抬|升|降)?"
r"(?:再|继续|接着|然后)?"
r"(?P<direction>向前|往前|朝前|前移|向后|往后|朝后|后移|"
r"向上|往上|抬高|抬起|升高|向下|往下|降低|降下|前|后|上|下)"
r"(?:移动|移|调节|调整|调|倾斜|抬|升|降)?(?P<delta>\d{{1,3}})%"
r"(?:移动|移|转动|旋转|转|调节|调整|调|设置|设|倾斜|抬|升|降)?"
r"(?P<delta>\d{{1,3}})%"
)
DIRECTIONAL_ABSOLUTE_PERCENT_PATTERN = re.compile(
r"(?P<direction>向前|往前|朝前|前移|向后|往后|朝后|后移|"
r"向上|往上|抬高|抬起|升高|向下|往下|降低|降下)"
r"(?:移动|移|转动|旋转|转|调节|调整|调|设置|设|倾斜|抬|升|降)*"
r"(?:到|至|为)"
r"(?P<position>[+-]?\d{1,3}(?:\.\d+)?)%"
)
DIRECTIONAL_RELATIVE_PERCENT_PATTERN = re.compile(
r"(?P<direction>向前|往前|朝前|前移|向后|往后|朝后|后移|"
r"向上|往上|抬高|抬起|升高|向下|往下|降低|降下)"
r"(?:移动|移|转动|旋转|转|调节|调整|调|设置|设|倾斜|抬|升|降)?"
r"(?P<delta>\d{1,3})%"
)
TIMED_MOTION_PATTERN_TEMPLATE = (
r"{alias}(?:持续|保持)?(?:再|继续|接着|然后)?"
r"(?:移动|移|转动|旋转|转|调节|调整|调|倾斜|抬|升|降)?"
r"(?:再|继续|接着|然后)?"
r"(?P<direction>向前|往前|朝前|前移|前倾|向后|往后|朝后|后移|后仰|"
r"向上|往上|抬高|抬起|升高|向下|往下|降低|降下|前|后|上|下)"
r"(?:移动|移|调节|调整|调|倾斜|抬|升|降|运行|运动|持续|保持|时长)*"
r"(?:移动|移|转动|旋转|转|调节|调整|调|倾斜|抬|升|降|运行|运动|持续|保持|时长)*"
r"(?P<duration>\d+(?:\.\d+)?)(?P<unit>毫秒|ms|秒|s|分钟|分|min)"
)
ZERO_POSITION_PATTERN = re.compile(r"归零|回零|复位|零位")
@@ -126,7 +153,14 @@ MOTION_AXIS_ALIASES = {
"longitudinal": ("水平", "前后", "纵向", "滑轨"),
# Internal capability name is retained for compatibility. On the
# seat-ota CAN0 firmware this board axis is the physical seat-height axis.
"cushion_tilt": ("座椅高度", "高度", "坐垫倾角", "坐盆", "坐垫"),
"cushion_tilt": (
"座椅高度",
"高度",
"坐垫倾角",
"坐盆倾角",
"坐盆",
"坐垫",
),
"backrest_recline": ("靠背", "椅背"),
"legrest_height": ("腿部支撑", "小腿托", "腿托", "脚托"),
}
@@ -222,6 +256,7 @@ def _normalize_text(value: str) -> str:
text = re.sub(r"座椅(?:吹风|送风)", "座椅通风", text)
text = re.sub(r"座椅(?:取暖|加温)", "座椅加热", text)
text = text.replace("座椅暖一下", "座椅加热打开")
text = text.replace("推托", "腿托")
def replace_percent(match: re.Match[str]) -> str:
number = _chinese_integer(match.group(1))
@@ -251,6 +286,74 @@ def _relative_direction(axis: str, token: str) -> str | None:
return None
def _directional_motion_axis(clause: str, direction: str) -> str | None:
"""Resolve an omitted passenger-seat axis from the movement direction."""
explicit_axes = [
axis
for axis, aliases in MOTION_AXIS_ALIASES.items()
if any(alias in clause for alias in aliases)
]
if len(explicit_axes) == 1:
return explicit_axes[0]
if explicit_axes:
return None
if TARGET_PATTERN.search(clause) is None and not re.search(r"座椅|座位", clause):
return None
if direction in {
"向前",
"往前",
"朝前",
"前移",
"向后",
"往后",
"朝后",
"后移",
}:
return "longitudinal"
if direction in {
"向上",
"往上",
"抬高",
"抬起",
"升高",
"向下",
"往下",
"降低",
"降下",
}:
return "cushion_tilt"
return None
def _absolute_percent_motion_action(
axis: str, requested_position_value: float
) -> dict[str, Any]:
requested_position_pct = _round_half_away_from_zero(requested_position_value)
position_pct = max(0, min(100, requested_position_pct))
action: dict[str, Any] = {
"target": "passenger",
"action_type": "position",
"axis": axis,
"position_pct": position_pct,
}
notices: list[str] = []
if requested_position_value != requested_position_pct:
notices.append(
f"{MOTION_AXIS_LABELS[axis]}请求位置"
f"{_format_numeric_request(requested_position_value)}%;"
f"已取整为{requested_position_pct}%。"
)
if requested_position_pct != position_pct:
notices.append(
f"取整后超过0到100%行程;已采用{position_pct}%的备用目标"
"并移动到对应机械末端。"
)
if notices:
action["user_notice"] = " ".join(notices)
return action
def _context_motion_state(
context: dict[str, Any] | None, axis: str
) -> dict[str, Any] | None:
@@ -982,6 +1085,32 @@ def parse_local_intent(
if shared_position is not None:
continue
directional_absolute_axes: set[str] = set()
directional_absolute_match = DIRECTIONAL_ABSOLUTE_PERCENT_PATTERN.search(clause)
if directional_absolute_match is not None:
axis = _directional_motion_axis(
clause, directional_absolute_match.group("direction")
)
if axis is not None:
directional_absolute_axes.add(axis)
target_zh = (
_nearest_preceding_target(
clause, directional_absolute_match.start()
)
or "副驾"
)
if "driver" in TARGETS[target_zh]:
ambiguities.append(
{"field": "target", "question": PASSENGER_ONLY_QUESTION}
)
else:
motion_actions.append(
_absolute_percent_motion_action(
axis,
float(directional_absolute_match.group("position")),
)
)
timed_axes: set[str] = set()
timed_aliases = {
**MOTION_AXIS_ALIASES,
@@ -1022,7 +1151,7 @@ def parse_local_intent(
relative_axes: set[str] = set()
for axis, aliases in MOTION_AXIS_ALIASES.items():
if axis in timed_axes:
if axis in timed_axes or axis in directional_absolute_axes:
continue
for alias in aliases:
angle_match = re.search(
@@ -1062,6 +1191,46 @@ def parse_local_intent(
motion_actions.append(action)
break
directional_relative_match = DIRECTIONAL_RELATIVE_PERCENT_PATTERN.search(clause)
if (
directional_relative_match is not None
and not relative_axes
and not directional_absolute_axes
):
axis = _directional_motion_axis(
clause, directional_relative_match.group("direction")
)
if axis is not None and axis not in timed_axes:
relative_axes.add(axis)
target_zh = (
_nearest_preceding_target(
clause, directional_relative_match.start()
)
or "副驾"
)
if "driver" in TARGETS[target_zh]:
ambiguities.append(
{"field": "target", "question": PASSENGER_ONLY_QUESTION}
)
else:
action, question = _relative_motion_action(
axis,
directional_relative_match.group("direction"),
int(directional_relative_match.group("delta")),
"percent",
context,
)
if action is None:
ambiguities.append(
{
"field": f"passenger.position.{axis}",
"question": question
or "无法计算座椅相对移动目标。",
}
)
else:
motion_actions.append(action)
endpoint_actions: dict[str, int] = {}
has_specific_non_longitudinal_axis = any(
any(alias in clause for alias in MOTION_AXIS_ALIASES[axis])
@@ -1078,7 +1247,7 @@ def parse_local_intent(
endpoint_actions["longitudinal"] = 0
elif re.search(r"最后面|最后|向后移到底|移到最后", clause):
endpoint_actions["longitudinal"] = 100
cushion_alias = r"(?:座椅高度|高度|坐垫倾角|坐盆|坐垫)"
cushion_alias = r"(?:座椅高度|高度|坐垫倾角|坐盆倾角|坐盆|坐垫)"
if re.search(
cushion_alias + r"(?:抬到最上方|调到最上方|移到最上方|最高|抬到头|抬到底)",
clause,
@@ -1128,7 +1297,12 @@ def parse_local_intent(
)
for axis, aliases in MOTION_AXIS_ALIASES.items():
if axis in endpoint_actions or axis in relative_axes or axis in timed_axes:
if (
axis in endpoint_actions
or axis in relative_axes
or axis in timed_axes
or axis in directional_absolute_axes
):
continue
for alias in aliases:
position_match = re.search(