This commit is contained in:
orin
2026-07-30 08:45:38 +08:00
commit 25b24ac4cd
1333 changed files with 170092 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
# Domain Plugins
每个子目录是一个完整的领域交付单元。领域插件只能依赖 `cognitive_agent.sdk` 和 `contracts/`,不得依赖其他领域或中央编排实现。
新领域从 `_template/` 复制,目录名和 `spec.domain` 必须使用相同的 ASCII snake_case 标识。插件注册由 `manifest.yaml` 驱动,不在中央代码中硬编码领域列表。
领域目录固定结构:
```text
<domain>/
├── README.md
├── OWNERS.yaml
├── manifest.yaml
├── config/
│ ├── capabilities.yaml
│ └── presets/
├── schemas/
│ └── function-parameters.schema.json
├── can/
│ └── signal-mapping.yaml
├── src/<domain>_domain/
└── tests/
├── unit/
├── contract/
└── integration/
```
+7
View File
@@ -0,0 +1,7 @@
domain: example_domain
primary_owner: TBD
reviewers:
- integration_owner
paths:
- domains/example_domain/**
+12
View File
@@ -0,0 +1,12 @@
# Domain Template
复制本目录后完成以下替换:
1. 把 `example_domain` 替换为稳定领域标识。
2. 把 `src/domain_plugin` 重命名为 `<domain>_domain`,同步修改 manifest entrypoint。
3. 填写真实负责人、能力、参数 Schema、preset 和信号映射。
4. `signal-mapping.yaml` 在通过 HIL 前保持 `status: draft`。
5. 补齐 Generator、Validator、Compiler、Telemetry Decoder 和测试。
领域插件不得保存 DBC 副本或直接发送 CAN。Compiler 返回符合公共 Schema 的 `CommandPlan`,公共 Runtime 负责安全门禁和发送。
+18
View File
@@ -0,0 +1,18 @@
apiVersion: cognitive-agent/v1alpha1
kind: SignalMapping
metadata:
id: example-domain-reference-mapping
name: Example domain reference vehicle mapping
version: 0.1.0
spec:
domain: example_domain
platform: reference_vehicle
dbc_ref: ../../../platforms/reference_vehicle/can/dbc/vehicle.dbc
status: draft
mappings:
- capability: example_domain.set_level
parameter: level_pct
compiler_handler: compile_set_level
calibration_ref: ../../../platforms/reference_vehicle/calibration/example_domain.yaml
unit: percent
+23
View File
@@ -0,0 +1,23 @@
apiVersion: cognitive-agent/v1alpha1
kind: CapabilityCatalog
metadata:
id: example-domain-capabilities
name: Example domain capabilities
version: 0.1.0
spec:
domain: example_domain
capabilities:
- name: example_domain.set_level
description: Replace with a user-facing semantic capability description.
target_devices:
- example_device
target_scopes:
- vehicle
parameters:
- name: level_pct
type: number
unit: percent
required: true
description: Replace with the physical meaning and range source.
risk_level: medium
confirmation_policy: on_ambiguity
+24
View File
@@ -0,0 +1,24 @@
apiVersion: cognitive-agent/v1alpha1
kind: FunctionPreset
metadata:
id: example-domain-default
name: Example preset
version: 0.1.0
spec:
domain: example_domain
operations:
- id: set_example_level
capability: example_domain.set_level
target:
device: example_device
parameters:
level_pct: 50
transition:
duration_ms: 1000
profile: device_default
timeout_ms: 5000
failure_policy: abort
locked_parameters: []
applicability:
platform: reference_vehicle
+16
View File
@@ -0,0 +1,16 @@
apiVersion: cognitive-agent/v1alpha1
kind: DomainManifest
metadata:
id: example-domain-plugin
name: Example domain plugin
version: 0.1.0
spec:
domain: example_domain
entrypoint: domain_plugin.plugin:create_plugin
function_schema: schemas/function-parameters.schema.json
capabilities: config/capabilities.yaml
signal_mapping: can/signal-mapping.yaml
supported_platforms:
- reference_vehicle
telemetry_required: true
+32
View File
@@ -0,0 +1,32 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://cognitive-agent.local/domains/example_domain/function-parameters.schema.json",
"title": "ExampleDomainFunctionParameters",
"type": "object",
"properties": {
"spec": {
"type": "object",
"properties": {
"domain": {"const": "example_domain"},
"operations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"capability": {"const": "example_domain.set_level"},
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["level_pct"],
"properties": {
"level_pct": {"type": "number", "minimum": 0, "maximum": 100}
}
}
}
}
}
}
}
}
}
+12
View File
@@ -0,0 +1,12 @@
# Plugin Source
建议实现以下模块,具体接口以 `cognitive_agent.sdk` 为准:
- `plugin.py`:创建并注册插件能力。
- `generator.py`:`IntentIR -> FunctionConfig`。
- `validator.py`:领域组合约束和上下文校验。
- `compiler.py`:`FunctionConfig + PlatformContext -> CommandPlan`。
- `telemetry.py`:CAN 回读到领域状态。
模块只返回数据或领域错误,不直接访问 LLM、数据库、HTTP 或 CAN socket。
+8
View File
@@ -0,0 +1,8 @@
# Domain Tests
- `unit/`:Generator、Validator、Compiler、Telemetry Decoder 的纯单元测试。
- `contract/`:manifest、能力目录、preset、Schema、映射和 SDK 插件契约。
- `integration/`:通过公共模拟 CAN Gateway 验证发送计划、回读、超时和失败行为。
硬件用例必须显式标记 `pytest.mark.hardware`,默认 CI 不运行。
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+8
View File
@@ -0,0 +1,8 @@
domain: ambient_light
primary_owner: ambient_light_owner
reviewers:
- integration_owner
- can_owner
paths:
- domains/ambient_light/**
+7
View File
@@ -0,0 +1,7 @@
# Ambient Light Domain
氛围灯领域已根据根目录 `空调-天幕-氛围灯CAN信号矩阵表.xlsx` 的“氛围灯”工作表完成 dry-run 闭环。
当前能力包括总开关、精确 `#RRGGBB` 颜色、0–100% 亮度,以及露营、小憩、观影、冥想和浪漫等矩阵原生模式。颜色和亮度使用 `0x32B/0x2B` 中的 IVI 1680 万色 RGB 信号,无需虚构颜色索引;总开关使用 `0x064/0x02`,场景模式使用 `0x496/0x04`。
动态效果不会由 Agent 逐帧生成。源表缺少完整时序、计数器、校验和和车型适用性,当前严格禁止实车发送。
+50
View File
@@ -0,0 +1,50 @@
version: 1.0.0
source:
file: 空调-天幕-氛围灯CAN信号矩阵表.xlsx
sheet: 氛围灯
scope: normalized_ambient_light_subset
hardware_execution_allowed: false
limitations:
- Source workbook is partial and omits bus timing, counter, checksum, and variant applicability.
- Exact RGB control uses the matrix-defined IVI 16.8-million-color request.
messages:
- name: Ambient_Power_Control_064_02
frame_id: 100
minimum_payload_bytes: 8
fixed_signals: {Sub_ID: 2}
signals:
- {name: Sub_ID, start_bit: 0, bit_length: 8}
- {name: Atomic_atmosphere_light_switch_S, start_bit: 56, bit_length: 2}
- name: Ambient_Mode_Control_496_04
frame_id: 1174
minimum_payload_bytes: 4
fixed_signals: {Sub_ID: 4}
signals:
- {name: Sub_ID, start_bit: 0, bit_length: 8}
- {name: atmosphere_Light_Request_Command_496_S, start_bit: 27, bit_length: 5}
- name: Ambient_RGB_Control_32B_2B
frame_id: 811
minimum_payload_bytes: 5
fixed_signals: {Sub_ID: 43}
signals:
- {name: Sub_ID, start_bit: 0, bit_length: 8}
- {name: IVI_1680Atmosphere_Color_R_Setting_S, start_bit: 8, bit_length: 8}
- {name: IVI_1680Atmosphere_Color_G_Setting_S, start_bit: 16, bit_length: 8}
- {name: IVI_1680Atmosphere_Color_B_Setting_S, start_bit: 24, bit_length: 8}
- {name: IVI_1680Atmosphere_Brightness_Setting_S, start_bit: 32, bit_length: 7}
- name: Ambient_Power_Status_3F3
frame_id: 1011
minimum_payload_bytes: 8
signals:
- {name: Atmosphere_Light_Switch_Fdback_S, start_bit: 62, bit_length: 2}
- name: Ambient_RGB_Status_1F1
frame_id: 497
minimum_payload_bytes: 48
signals:
- {name: LFDoor_Flowing_Ambient_Light_Brightness_S, start_bit: 40, bit_length: 7}
- {name: Front_Atmspr_Light_Color_R_S, start_bit: 336, bit_length: 8}
- {name: Front_Atmspr_Light_Color_G_S, start_bit: 344, bit_length: 8}
- {name: Front_Atmspr_Light_Color_B_S, start_bit: 352, bit_length: 8}
- {name: Rear_Atmspr_Light_Color_R_S, start_bit: 360, bit_length: 8}
- {name: Rear_Atmspr_Light_Color_G_S, start_bit: 368, bit_length: 8}
- {name: Rear_Atmspr_Light_Color_B_S, start_bit: 376, bit_length: 8}
+55
View File
@@ -0,0 +1,55 @@
apiVersion: cognitive-agent/v1alpha1
kind: SignalMapping
metadata:
id: ambient-light-reference-vehicle-mapping
name: Ambient-light mapping from supplied CAN matrix
version: 1.0.0
description: Virtual-only mapping using matrix-defined power, mode, RGB and brightness signals.
spec:
domain: ambient_light
platform: reference_vehicle
dbc_ref: ../../../platforms/reference_vehicle/can/dbc/vehicle.dbc
matrix_ref: can-matrix.yaml
status: draft
mappings:
- capability: ambient_light.set_power
parameter: state
target_zone: vehicle
compiler_handler: compile_enum
value_map: {"on": 1, "off": 2}
bus: cabin_can
message: Ambient_Power_Control_064_02
signal: Atomic_atmosphere_light_switch_S
feedback_message: Ambient_Power_Status_3F3
feedback_signal: Atmosphere_Light_Switch_Fdback_S
feedback_handler: compile_enum
feedback_value_map: {"on": 2, "off": 1}
- capability: ambient_light.set_illumination
parameter: color_hex
target_zone: vehicle
compiler_handler: compile_hex_rgb
bus: cabin_can
message: Ambient_RGB_Control_32B_2B
signals:
- IVI_1680Atmosphere_Color_R_Setting_S
- IVI_1680Atmosphere_Color_G_Setting_S
- IVI_1680Atmosphere_Color_B_Setting_S
- capability: ambient_light.set_illumination
parameter: brightness_pct
target_zone: vehicle
compiler_handler: compile_integer
bus: cabin_can
message: Ambient_RGB_Control_32B_2B
signal: IVI_1680Atmosphere_Brightness_Setting_S
feedback_message: Ambient_RGB_Status_1F1
feedback_signal: LFDoor_Flowing_Ambient_Light_Brightness_S
feedback_handler: compile_integer
unit: percent
- capability: ambient_light.set_mode
parameter: mode
target_zone: vehicle
compiler_handler: compile_enum
value_map: {camping: 1, rest: 2, cinema: 3, meditation: 4, romantic: 5, "off": 6, "on": 7}
bus: cabin_can
message: Ambient_Mode_Control_496_04
signal: atmosphere_Light_Request_Command_496_S
+51
View File
@@ -0,0 +1,51 @@
apiVersion: cognitive-agent/v1alpha1
kind: CapabilityCatalog
metadata:
id: ambient-light-capabilities
name: Ambient light capabilities
version: 1.0.0
spec:
domain: ambient_light
capabilities:
- name: ambient_light.set_power
description: Turn the vehicle ambient-light system on or off.
target_devices: [ambient_light]
target_scopes: [vehicle]
parameters:
- name: state
type: string
required: true
allowed_values: ["on", "off"]
description: Absolute ambient-light power state.
risk_level: low
confirmation_policy: on_ambiguity
- name: ambient_light.set_illumination
description: Set exact 24-bit RGB color and brightness for the cabin ambient light.
target_devices: [ambient_light]
target_scopes: [vehicle]
parameters:
- name: color_hex
type: string
required: true
description: RGB color encoded as #RRGGBB; infer a suitable color for semantic requests.
- name: brightness_pct
type: integer
unit: percent
minimum: 0
maximum: 100
required: true
description: Absolute brightness percentage.
risk_level: low
confirmation_policy: on_ambiguity
- name: ambient_light.set_mode
description: Select a matrix-defined ambient-light scene mode.
target_devices: [ambient_light]
target_scopes: [vehicle]
parameters:
- name: mode
type: string
required: true
allowed_values: [camping, rest, cinema, meditation, romantic, "off", "on"]
description: Absolute scene mode.
risk_level: low
confirmation_policy: on_ambiguity
+26
View File
@@ -0,0 +1,26 @@
apiVersion: cognitive-agent/v1alpha1
kind: FunctionPreset
metadata:
id: ambient-light-relax
name: Ambient light relaxation preset
version: 1.0.0
description: Virtual visual preset using the matrix-defined 24-bit RGB request.
spec:
domain: ambient_light
operations:
- id: set_relax_lighting
capability: ambient_light.set_illumination
target:
device: ambient_light
zone: vehicle
parameters:
color_hex: "#2E6F9E"
brightness_pct: 30
transition:
duration_ms: 1200
profile: ease_in_out
timeout_ms: 5000
failure_policy: continue
locked_parameters: []
applicability:
platform: reference_vehicle
+16
View File
@@ -0,0 +1,16 @@
apiVersion: cognitive-agent/v1alpha1
kind: DomainManifest
metadata:
id: ambient-light-domain-plugin
name: Ambient light domain plugin
version: 0.1.0
spec:
domain: ambient_light
entrypoint: ambient_light_domain.plugin:create_plugin
function_schema: schemas/function-parameters.schema.json
capabilities: config/capabilities.yaml
signal_mapping: can/signal-mapping.yaml
supported_platforms:
- reference_vehicle
telemetry_required: true
@@ -0,0 +1,59 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://cognitive-agent.local/domains/ambient_light/function-parameters.schema.json",
"title": "AmbientLightFunctionParameters",
"type": "object",
"properties": {
"spec": {
"type": "object",
"properties": {
"domain": {"const": "ambient_light"},
"operations": {
"type": "array",
"items": {
"oneOf": [
{
"properties": {
"capability": {"const": "ambient_light.set_power"},
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["state"],
"properties": {"state": {"enum": ["on", "off"]}}
}
}
},
{
"properties": {
"capability": {"const": "ambient_light.set_illumination"},
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["color_hex", "brightness_pct"],
"properties": {
"color_hex": {"type": "string", "pattern": "^#[0-9A-Fa-f]{6}$"},
"brightness_pct": {"type": "integer", "minimum": 0, "maximum": 100}
}
}
}
},
{
"properties": {
"capability": {"const": "ambient_light.set_mode"},
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["mode"],
"properties": {
"mode": {"enum": ["camping", "rest", "cinema", "meditation", "romantic", "off", "on"]}
}
}
}
}
]
}
}
}
}
}
}
+4
View File
@@ -0,0 +1,4 @@
# Ambient Light Plugin Source
实现 `plugin.py`、`generator.py`、`validator.py`、`compiler.py` 和 `telemetry.py`。颜色、亮度和效果必须转换为 ECU 支持的稳定命令;不在插件内实现无限制逐帧 CAN 动画。
+4
View File
@@ -0,0 +1,4 @@
# Ambient Light Tests
覆盖颜色格式、亮度边界、灯区支持、效果降级、亮度曲线、回读和超时。动态效果测试需验证命令速率上限。
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+8
View File
@@ -0,0 +1,8 @@
domain: climate
primary_owner: climate_owner
reviewers:
- integration_owner
- can_owner
paths:
- domains/climate/**
+7
View File
@@ -0,0 +1,7 @@
# Climate Domain
空调领域已根据根目录 `空调-天幕-氛围灯CAN信号矩阵表.xlsx` 的“空调”工作表完成 dry-run 闭环。
当前能力包括分区电源、18–32℃ 温度、整车 0–7 档风量、A/C、内外循环、分区吹面/吹脚和前除霜。`can/can-matrix.yaml` 保留来源信号名、帧 ID、Sub ID、Start Bit 和 Bit Length;`can/signal-mapping.yaml` 负责把稳定语义参数确定性转换为矩阵原始值。
命令主要使用 `0x064` 的 `0x03`–`0x06` 子帧,虚拟反馈使用 `0x0DB` 和 `0x404`。源表未提供完整周期、计数器、校验和、总线通道和车型适用性,`hardware_execution_allowed` 固定为 `false`。
+77
View File
@@ -0,0 +1,77 @@
version: 1.0.0
source:
file: 空调-天幕-氛围灯CAN信号矩阵表.xlsx
sheet: 空调
scope: normalized_climate_subset
hardware_execution_allowed: false
limitations:
- Source workbook is a partial matrix without bus timing, counters, checksums, or vehicle variant applicability.
- Sub IDs are encoded as the first payload byte for virtual execution.
messages:
- name: Climate_Control_064_03
frame_id: 100
minimum_payload_bytes: 8
fixed_signals: {Sub_ID: 3}
signals:
- {name: Sub_ID, start_bit: 0, bit_length: 8}
- {name: Atomic_AC_S, start_bit: 36, bit_length: 2}
- {name: Atomic_cycle_mode_S, start_bit: 38, bit_length: 2}
- {name: Atomic_defrosting_S, start_bit: 40, bit_length: 2}
- {name: Atomic_vehicle_air_conditioning_switch_S, start_bit: 50, bit_length: 2}
- {name: Atomic_front_air_conditioning_switch_S, start_bit: 52, bit_length: 2}
- {name: Atomic_rear_air_conditioning_switch_S, start_bit: 54, bit_length: 2}
- {name: Atomic_driver_air_conditioning_switch_S, start_bit: 56, bit_length: 2}
- {name: Atomic_passenger_air_conditioning_switch_S, start_bit: 58, bit_length: 2}
- {name: Atomic_left_rear_air_conditioning_switch_S, start_bit: 60, bit_length: 2}
- {name: Atomic_right_rear_air_conditioning_switch_S, start_bit: 62, bit_length: 2}
- name: Climate_Control_064_04
frame_id: 100
minimum_payload_bytes: 8
fixed_signals: {Sub_ID: 4}
signals:
- {name: Sub_ID, start_bit: 0, bit_length: 8}
- {name: Atomic_vehicle_air_volume_S, start_bit: 8, bit_length: 4}
- {name: Atomic_vehicle_air_conditioning_temperature_S, start_bit: 40, bit_length: 8}
- {name: Atomic_front_air_conditioning_temperature_S, start_bit: 48, bit_length: 8}
- {name: Atomic_rear_air_conditioning_temperature_S, start_bit: 56, bit_length: 8}
- name: Climate_Control_064_05
frame_id: 100
minimum_payload_bytes: 8
fixed_signals: {Sub_ID: 5}
signals:
- {name: Sub_ID, start_bit: 0, bit_length: 8}
- {name: Atomic_driver_air_conditioning_temperature_S, start_bit: 8, bit_length: 8}
- {name: Atomic_passenger_air_conditioning_temperature_S, start_bit: 16, bit_length: 8}
- {name: Atomic_left_rear_air_conditioning_temperature_S, start_bit: 24, bit_length: 8}
- {name: Atomic_right_rear_air_conditioning_temperature_S, start_bit: 32, bit_length: 8}
- {name: Atomic_driver_air_conditioning_outlet_mode_S, start_bit: 56, bit_length: 4}
- {name: Atomic_left_rear_air_conditioning_outlet_mode_S, start_bit: 60, bit_length: 4}
- name: Climate_Control_064_06
frame_id: 100
minimum_payload_bytes: 4
fixed_signals: {Sub_ID: 6}
signals:
- {name: Sub_ID, start_bit: 0, bit_length: 8}
- {name: Atomic_co_pilot_air_conditioning_outlet_mode_S, start_bit: 8, bit_length: 4}
- {name: Atomic_right_rear_air_conditioning_outlet_mode_S, start_bit: 16, bit_length: 4}
- name: Climate_Status_0DB
frame_id: 219
minimum_payload_bytes: 8
signals:
- {name: ACHMC_AC_Open_Rslt_Fdbck_Infor_S, start_bit: 0, bit_length: 2}
- {name: AV_Close_Results_Feedback_S, start_bit: 2, bit_length: 2}
- {name: AC_Cycle_Mode_0DB_S, start_bit: 4, bit_length: 2}
- {name: Medium_AC_Switch_S, start_bit: 6, bit_length: 2}
- {name: ACHMC_Mast_Dri_Temp_Set_Rslt_S, start_bit: 8, bit_length: 5}
- {name: Co_Pilot_AC_Temp_Set_S, start_bit: 16, bit_length: 5}
- {name: AC_Wind_Model_0DB_S, start_bit: 28, bit_length: 4}
- {name: IKEY_AC_Air_Gear_S, start_bit: 32, bit_length: 4}
- {name: ACHMC_AC_Front_Defrosting_Fdbck_S, start_bit: 41, bit_length: 2}
- name: Climate_Status_404
frame_id: 1028
minimum_payload_bytes: 4
signals:
- {name: ACHMC_AC_On_Status_S, start_bit: 8, bit_length: 2}
- {name: AC_Cycle_Mode_404_S, start_bit: 12, bit_length: 2}
- {name: AC_Wind_Model_404_S, start_bit: 16, bit_length: 4}
- {name: Ac_wind_rnk_S, start_bit: 20, bit_length: 4}
+36
View File
@@ -0,0 +1,36 @@
apiVersion: cognitive-agent/v1alpha1
kind: SignalMapping
metadata:
id: climate-reference-vehicle-mapping
name: Climate mapping from supplied CAN matrix
version: 1.0.0
description: Virtual-only mapping normalized from the supplied climate worksheet.
spec:
domain: climate
platform: reference_vehicle
dbc_ref: ../../../platforms/reference_vehicle/can/dbc/vehicle.dbc
matrix_ref: can-matrix.yaml
status: draft
mappings:
- {capability: climate.set_power, parameter: state, target_zone: vehicle, compiler_handler: compile_enum, bus: cabin_can, message: Climate_Control_064_03, signal: Atomic_vehicle_air_conditioning_switch_S, value_map: {"on": 1, "off": 2}, feedback_message: Climate_Status_404, feedback_signal: ACHMC_AC_On_Status_S, feedback_handler: compile_enum, feedback_value_map: {"on": 1, "off": 2}}
- {capability: climate.set_power, parameter: state, target_zone: front_row, compiler_handler: compile_enum, bus: cabin_can, message: Climate_Control_064_03, signal: Atomic_front_air_conditioning_switch_S, value_map: {"on": 1, "off": 2}}
- {capability: climate.set_power, parameter: state, target_zone: rear_row, compiler_handler: compile_enum, bus: cabin_can, message: Climate_Control_064_03, signal: Atomic_rear_air_conditioning_switch_S, value_map: {"on": 1, "off": 2}}
- {capability: climate.set_power, parameter: state, target_zone: driver, compiler_handler: compile_enum, bus: cabin_can, message: Climate_Control_064_03, signal: Atomic_driver_air_conditioning_switch_S, value_map: {"on": 1, "off": 2}}
- {capability: climate.set_power, parameter: state, target_zone: passenger, compiler_handler: compile_enum, bus: cabin_can, message: Climate_Control_064_03, signal: Atomic_passenger_air_conditioning_switch_S, value_map: {"on": 1, "off": 2}}
- {capability: climate.set_power, parameter: state, target_zone: rear_left, compiler_handler: compile_enum, bus: cabin_can, message: Climate_Control_064_03, signal: Atomic_left_rear_air_conditioning_switch_S, value_map: {"on": 1, "off": 2}}
- {capability: climate.set_power, parameter: state, target_zone: rear_right, compiler_handler: compile_enum, bus: cabin_can, message: Climate_Control_064_03, signal: Atomic_right_rear_air_conditioning_switch_S, value_map: {"on": 1, "off": 2}}
- {capability: climate.set_temperature, parameter: temperature_c, target_zone: vehicle, compiler_handler: compile_integer, bus: cabin_can, message: Climate_Control_064_04, signal: Atomic_vehicle_air_conditioning_temperature_S, unit: celsius}
- {capability: climate.set_temperature, parameter: temperature_c, target_zone: front_row, compiler_handler: compile_integer, bus: cabin_can, message: Climate_Control_064_04, signal: Atomic_front_air_conditioning_temperature_S, unit: celsius}
- {capability: climate.set_temperature, parameter: temperature_c, target_zone: rear_row, compiler_handler: compile_integer, bus: cabin_can, message: Climate_Control_064_04, signal: Atomic_rear_air_conditioning_temperature_S, unit: celsius}
- {capability: climate.set_temperature, parameter: temperature_c, target_zone: driver, compiler_handler: compile_integer, bus: cabin_can, message: Climate_Control_064_05, signal: Atomic_driver_air_conditioning_temperature_S, feedback_message: Climate_Status_0DB, feedback_signal: ACHMC_Mast_Dri_Temp_Set_Rslt_S, feedback_handler: compile_temperature_feedback, unit: celsius}
- {capability: climate.set_temperature, parameter: temperature_c, target_zone: passenger, compiler_handler: compile_integer, bus: cabin_can, message: Climate_Control_064_05, signal: Atomic_passenger_air_conditioning_temperature_S, feedback_message: Climate_Status_0DB, feedback_signal: Co_Pilot_AC_Temp_Set_S, feedback_handler: compile_temperature_feedback, unit: celsius}
- {capability: climate.set_temperature, parameter: temperature_c, target_zone: rear_left, compiler_handler: compile_integer, bus: cabin_can, message: Climate_Control_064_05, signal: Atomic_left_rear_air_conditioning_temperature_S, unit: celsius}
- {capability: climate.set_temperature, parameter: temperature_c, target_zone: rear_right, compiler_handler: compile_integer, bus: cabin_can, message: Climate_Control_064_05, signal: Atomic_right_rear_air_conditioning_temperature_S, unit: celsius}
- {capability: climate.set_fan_level, parameter: level, target_zone: vehicle, compiler_handler: compile_level_plus_one, bus: cabin_can, message: Climate_Control_064_04, signal: Atomic_vehicle_air_volume_S, feedback_message: Climate_Status_404, feedback_signal: Ac_wind_rnk_S, feedback_handler: compile_integer}
- {capability: climate.set_ac, parameter: state, target_zone: vehicle, compiler_handler: compile_enum, bus: cabin_can, message: Climate_Control_064_03, signal: Atomic_AC_S, value_map: {"on": 1, "off": 2}, feedback_message: Climate_Status_0DB, feedback_signal: Medium_AC_Switch_S, feedback_handler: compile_enum, feedback_value_map: {"on": 2, "off": 1}}
- {capability: climate.set_cycle_mode, parameter: mode, target_zone: vehicle, compiler_handler: compile_enum, bus: cabin_can, message: Climate_Control_064_03, signal: Atomic_cycle_mode_S, value_map: {internal: 1, external: 2}, feedback_message: Climate_Status_0DB, feedback_signal: AC_Cycle_Mode_0DB_S, feedback_handler: compile_enum, feedback_value_map: {internal: 2, external: 1}}
- {capability: climate.set_front_defrost, parameter: state, target_zone: vehicle, compiler_handler: compile_enum, bus: cabin_can, message: Climate_Control_064_03, signal: Atomic_defrosting_S, value_map: {"on": 1, "off": 2}, feedback_message: Climate_Status_0DB, feedback_signal: ACHMC_AC_Front_Defrosting_Fdbck_S, feedback_handler: compile_enum, feedback_value_map: {"on": 2, "off": 1}}
- {capability: climate.set_outlet_mode, parameter: mode, target_zone: driver, compiler_handler: compile_enum, bus: cabin_can, message: Climate_Control_064_05, signal: Atomic_driver_air_conditioning_outlet_mode_S, value_map: {face: 1, feet: 2}, feedback_message: Climate_Status_0DB, feedback_signal: AC_Wind_Model_0DB_S, feedback_handler: compile_enum, feedback_value_map: {face: 1, feet: 3}}
- {capability: climate.set_outlet_mode, parameter: mode, target_zone: rear_left, compiler_handler: compile_enum, bus: cabin_can, message: Climate_Control_064_05, signal: Atomic_left_rear_air_conditioning_outlet_mode_S, value_map: {face: 1, feet: 2}}
- {capability: climate.set_outlet_mode, parameter: mode, target_zone: passenger, compiler_handler: compile_enum, bus: cabin_can, message: Climate_Control_064_06, signal: Atomic_co_pilot_air_conditioning_outlet_mode_S, value_map: {face: 1, feet: 2}, feedback_message: Climate_Status_0DB, feedback_signal: AC_Wind_Model_0DB_S, feedback_handler: compile_enum, feedback_value_map: {face: 1, feet: 3}}
- {capability: climate.set_outlet_mode, parameter: mode, target_zone: rear_right, compiler_handler: compile_enum, bus: cabin_can, message: Climate_Control_064_06, signal: Atomic_right_rear_air_conditioning_outlet_mode_S, value_map: {face: 1, feet: 2}}
+96
View File
@@ -0,0 +1,96 @@
apiVersion: cognitive-agent/v1alpha1
kind: CapabilityCatalog
metadata:
id: climate-capabilities
name: Climate capabilities
version: 1.0.0
spec:
domain: climate
capabilities:
- name: climate.set_power
description: Turn HVAC power on or off for the requested cabin zone.
target_devices: [hvac]
target_scopes: [driver, passenger, front_row, rear_row, rear_left, rear_right, vehicle]
parameters:
- name: state
type: string
required: true
allowed_values: ["on", "off"]
description: Absolute HVAC power state.
risk_level: low
confirmation_policy: on_ambiguity
- name: climate.set_temperature
description: Set an absolute HVAC temperature from 18 to 32 degrees Celsius.
target_devices: [hvac]
target_scopes: [driver, passenger, front_row, rear_row, rear_left, rear_right, vehicle]
parameters:
- name: temperature_c
type: number
unit: celsius
minimum: 18
maximum: 32
required: true
description: Absolute requested temperature in degrees Celsius.
risk_level: medium
confirmation_policy: on_ambiguity
- name: climate.set_fan_level
description: Set whole-cabin airflow to an absolute integer level from 0 to 7.
target_devices: [hvac]
target_scopes: [vehicle]
parameters:
- name: level
type: integer
minimum: 0
maximum: 7
required: true
description: Absolute fan level, where 0 is off and 7 is maximum.
risk_level: low
confirmation_policy: on_ambiguity
- name: climate.set_ac
description: Turn the air-conditioning compressor request on or off.
target_devices: [hvac]
target_scopes: [vehicle]
parameters:
- name: state
type: string
required: true
allowed_values: ["on", "off"]
description: Absolute compressor request state.
risk_level: low
confirmation_policy: on_ambiguity
- name: climate.set_cycle_mode
description: Select internal or external cabin air circulation.
target_devices: [hvac]
target_scopes: [vehicle]
parameters:
- name: mode
type: string
required: true
allowed_values: [internal, external]
description: Absolute circulation mode.
risk_level: low
confirmation_policy: on_ambiguity
- name: climate.set_outlet_mode
description: Direct a zone outlet toward the face or feet.
target_devices: [hvac]
target_scopes: [driver, passenger, rear_left, rear_right]
parameters:
- name: mode
type: string
required: true
allowed_values: [face, feet]
description: Absolute outlet direction.
risk_level: low
confirmation_policy: on_ambiguity
- name: climate.set_front_defrost
description: Turn front windshield defrost on or off.
target_devices: [hvac]
target_scopes: [vehicle]
parameters:
- name: state
type: string
required: true
allowed_values: ["on", "off"]
description: Absolute front defrost state.
risk_level: medium
confirmation_policy: on_ambiguity
+36
View File
@@ -0,0 +1,36 @@
apiVersion: cognitive-agent/v1alpha1
kind: FunctionPreset
metadata:
id: climate-comfort
name: Climate comfort preset
version: 1.0.0
description: Virtual comfort values backed by the supplied partial matrix.
spec:
domain: climate
operations:
- id: set_cabin_temperature
capability: climate.set_temperature
target:
device: hvac
zone: vehicle
parameters:
temperature_c: 23
transition:
profile: device_default
timeout_ms: 5000
failure_policy: continue
- id: set_cabin_fan
capability: climate.set_fan_level
target:
device: hvac
zone: vehicle
parameters:
level: 3
transition:
duration_ms: 1500
profile: ease_in_out
timeout_ms: 5000
failure_policy: continue
locked_parameters: []
applicability:
platform: reference_vehicle
+16
View File
@@ -0,0 +1,16 @@
apiVersion: cognitive-agent/v1alpha1
kind: DomainManifest
metadata:
id: climate-domain-plugin
name: Climate domain plugin
version: 0.1.0
spec:
domain: climate
entrypoint: climate_domain.plugin:create_plugin
function_schema: schemas/function-parameters.schema.json
capabilities: config/capabilities.yaml
signal_mapping: can/signal-mapping.yaml
supported_platforms:
- reference_vehicle
telemetry_required: true
+76
View File
@@ -0,0 +1,76 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://cognitive-agent.local/domains/climate/function-parameters.schema.json",
"title": "ClimateFunctionParameters",
"type": "object",
"properties": {
"spec": {
"type": "object",
"properties": {
"domain": {"const": "climate"},
"operations": {
"type": "array",
"items": {
"oneOf": [
{
"properties": {
"capability": {"enum": ["climate.set_power", "climate.set_ac", "climate.set_front_defrost"]},
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["state"],
"properties": {"state": {"enum": ["on", "off"]}}
}
}
},
{
"properties": {
"capability": {"const": "climate.set_temperature"},
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["temperature_c"],
"properties": {"temperature_c": {"type": "number", "minimum": 18, "maximum": 32}}
}
}
},
{
"properties": {
"capability": {"const": "climate.set_fan_level"},
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["level"],
"properties": {"level": {"type": "integer", "minimum": 0, "maximum": 7}}
}
}
},
{
"properties": {
"capability": {"const": "climate.set_cycle_mode"},
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["mode"],
"properties": {"mode": {"enum": ["internal", "external"]}}
}
}
},
{
"properties": {
"capability": {"const": "climate.set_outlet_mode"},
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["mode"],
"properties": {"mode": {"enum": ["face", "feet"]}}
}
}
}
]
}
}
}
}
}
}
+4
View File
@@ -0,0 +1,4 @@
# Climate Plugin Source
实现 `plugin.py`、`generator.py`、`validator.py`、`compiler.py` 和 `telemetry.py`。分区、摄氏温度、百分比风量必须在 Compiler 中转换为平台定义的 message/signal,不直接发送 CAN。
+4
View File
@@ -0,0 +1,4 @@
# Climate Tests
覆盖温度/风量边界、不同 zone、自动模式冲突、平台不支持、发送超时和状态回读。`integration/` 使用模拟 CAN Gateway;HIL/实车用例单独标记。
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+8
View File
@@ -0,0 +1,8 @@
domain: roof
primary_owner: roof_owner
reviewers:
- integration_owner
- can_owner
paths:
- domains/roof/**
+7
View File
@@ -0,0 +1,7 @@
# Roof Domain
天幕领域已根据根目录 `空调-天幕-氛围灯CAN信号矩阵表.xlsx` 的“天幕”工作表完成 dry-run 闭环。
当前能力包括调光天幕 0–100% 透光率、后天幕遮阳帘 0–100% 绝对位置和自动调光开关。透光率使用 `0x4C1/0x13`,遮阳帘使用 `0x3E6/0x04`;虚拟反馈分别使用 `0x4BB` 和 `0x47F`,并保留初始化、霍尔和热保护信号作为后续安全接入点。
本文约定遮阳帘 `position_pct` 为 0% 完全关闭、100% 完全打开。源表缺少车型适用性和完整安全互锁,当前严格禁止实车发送。
+43
View File
@@ -0,0 +1,43 @@
version: 1.0.0
source:
file: 空调-天幕-氛围灯CAN信号矩阵表.xlsx
sheet: 天幕
scope: normalized_roof_subset
hardware_execution_allowed: false
limitations:
- Source workbook is partial and vehicle-variant applicability is not supplied.
- Anti-pinch and motor fault signals are feedback-only safety evidence in this dry-run.
messages:
- name: Roof_Glass_Control_4C1_13
frame_id: 1217
minimum_payload_bytes: 4
fixed_signals: {Sub_ID: 19}
signals:
- {name: Sub_ID, start_bit: 0, bit_length: 8}
- {name: Adjust_The_Skylight_Transmittance_Command_S, start_bit: 16, bit_length: 8}
- {name: Canopy_Automatic_Dimming_0x4C1_S, start_bit: 24, bit_length: 2}
- name: Roof_Shade_Control_3E6_04
frame_id: 998
minimum_payload_bytes: 5
fixed_signals: {Sub_ID: 4}
signals:
- {name: Sub_ID, start_bit: 0, bit_length: 8}
- {name: Rear_Awning_Shade_Screen_Position_Percentage_S, start_bit: 32, bit_length: 8}
- name: Roof_Glass_Status_4BB
frame_id: 1211
minimum_payload_bytes: 57
signals:
- {name: Current_Transmittance_Of_Skylight_S, start_bit: 444, bit_length: 4}
- {name: Current_Status_Of_Dimming_Sunroof_S, start_bit: 448, bit_length: 3}
- {name: Canopy_Automatic_Dimming_0x4BB_S, start_bit: 452, bit_length: 2}
- name: Roof_Shade_Status_47F
frame_id: 1151
minimum_payload_bytes: 7
signals:
- {name: Percentage_Of_Rear_Canopy_Position_47F_S, start_bit: 0, bit_length: 8}
- {name: The_Back_Canopy_Closed_State_47F_S, start_bit: 8, bit_length: 2}
- {name: Back_Canopy_Initialization_State_47F_S, start_bit: 10, bit_length: 2}
- {name: The_Current_Action_Status_Of_The_Back_Sky_47F_S, start_bit: 16, bit_length: 3}
- {name: Hall_Signal_Of_Rear_Canopy_Motor_A47F_S, start_bit: 21, bit_length: 1}
- {name: Hall_Signal_Of_Rear_Canopy_Motor_B47F_S, start_bit: 22, bit_length: 1}
- {name: Thermal_Protection_Status_Of_Rear_Canopy_Motor_47F_S, start_bit: 48, bit_length: 1}
+48
View File
@@ -0,0 +1,48 @@
apiVersion: cognitive-agent/v1alpha1
kind: SignalMapping
metadata:
id: roof-reference-vehicle-mapping
name: Roof mapping from supplied CAN matrix
version: 1.0.0
description: Virtual-only mapping normalized from the supplied roof worksheet.
spec:
domain: roof
platform: reference_vehicle
dbc_ref: ../../../platforms/reference_vehicle/can/dbc/vehicle.dbc
matrix_ref: can-matrix.yaml
status: draft
mappings:
- capability: roof.set_shade_position
parameter: position_pct
target_zone: vehicle
compiler_handler: compile_integer
bus: cabin_can
message: Roof_Shade_Control_3E6_04
signal: Rear_Awning_Shade_Screen_Position_Percentage_S
feedback_message: Roof_Shade_Status_47F
feedback_signal: Percentage_Of_Rear_Canopy_Position_47F_S
feedback_handler: compile_integer
unit: percent
- capability: roof.set_transmittance
parameter: transmittance_pct
target_zone: vehicle
compiler_handler: compile_integer
bus: cabin_can
message: Roof_Glass_Control_4C1_13
signal: Adjust_The_Skylight_Transmittance_Command_S
feedback_message: Roof_Glass_Status_4BB
feedback_signal: Current_Transmittance_Of_Skylight_S
feedback_handler: compile_transmittance_bucket
unit: percent
- capability: roof.set_automatic_dimming
parameter: state
target_zone: vehicle
compiler_handler: compile_enum
value_map: {"on": 1, "off": 2}
bus: cabin_can
message: Roof_Glass_Control_4C1_13
signal: Canopy_Automatic_Dimming_0x4C1_S
feedback_message: Roof_Glass_Status_4BB
feedback_signal: Canopy_Automatic_Dimming_0x4BB_S
feedback_handler: compile_enum
feedback_value_map: {"on": 1, "off": 2}
+49
View File
@@ -0,0 +1,49 @@
apiVersion: cognitive-agent/v1alpha1
kind: CapabilityCatalog
metadata:
id: roof-capabilities
name: Roof capabilities
version: 1.0.0
spec:
domain: roof
capabilities:
- name: roof.set_shade_position
description: Set the rear panoramic roof shade to an absolute position, 0 closed and 100 open.
target_devices: [roof_shade]
target_scopes: [vehicle]
parameters:
- name: position_pct
type: integer
unit: percent
minimum: 0
maximum: 100
required: true
description: Absolute shade opening percentage.
risk_level: high
confirmation_policy: on_ambiguity
- name: roof.set_transmittance
description: Set dimming canopy light transmittance, 0 darkest and 100 clearest.
target_devices: [roof_glass]
target_scopes: [vehicle]
parameters:
- name: transmittance_pct
type: integer
unit: percent
minimum: 0
maximum: 100
required: true
description: Absolute smart-glass transmittance percentage.
risk_level: medium
confirmation_policy: on_ambiguity
- name: roof.set_automatic_dimming
description: Turn automatic canopy dimming on or off.
target_devices: [roof_glass]
target_scopes: [vehicle]
parameters:
- name: state
type: string
required: true
allowed_values: ["on", "off"]
description: Absolute automatic dimming state.
risk_level: low
confirmation_policy: on_ambiguity
+24
View File
@@ -0,0 +1,24 @@
apiVersion: cognitive-agent/v1alpha1
kind: FunctionPreset
metadata:
id: roof-open-view
name: Roof open-view preset
version: 1.0.0
description: Example preset pending platform safety approval.
spec:
domain: roof
operations:
- id: open_roof_shade
capability: roof.set_shade_position
target:
device: roof_shade
zone: vehicle
parameters:
position_pct: 100
transition:
profile: device_default
timeout_ms: 15000
failure_policy: abort
locked_parameters: []
applicability:
vehicle_speed_state: stationary
+16
View File
@@ -0,0 +1,16 @@
apiVersion: cognitive-agent/v1alpha1
kind: DomainManifest
metadata:
id: roof-domain-plugin
name: Roof domain plugin
version: 0.1.0
spec:
domain: roof
entrypoint: roof_domain.plugin:create_plugin
function_schema: schemas/function-parameters.schema.json
capabilities: config/capabilities.yaml
signal_mapping: can/signal-mapping.yaml
supported_platforms:
- reference_vehicle
telemetry_required: true
+54
View File
@@ -0,0 +1,54 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://cognitive-agent.local/domains/roof/function-parameters.schema.json",
"title": "RoofFunctionParameters",
"type": "object",
"properties": {
"spec": {
"type": "object",
"properties": {
"domain": {"const": "roof"},
"operations": {
"type": "array",
"items": {
"oneOf": [
{
"properties": {
"capability": {"const": "roof.set_shade_position"},
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["position_pct"],
"properties": {"position_pct": {"type": "integer", "minimum": 0, "maximum": 100}}
}
}
},
{
"properties": {
"capability": {"const": "roof.set_transmittance"},
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["transmittance_pct"],
"properties": {"transmittance_pct": {"type": "integer", "minimum": 0, "maximum": 100}}
}
}
},
{
"properties": {
"capability": {"const": "roof.set_automatic_dimming"},
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["state"],
"properties": {"state": {"enum": ["on", "off"]}}
}
}
}
]
}
}
}
}
}
}
+4
View File
@@ -0,0 +1,4 @@
# Roof Plugin Source
实现 `plugin.py`、`generator.py`、`validator.py`、`compiler.py` 和 `telemetry.py`。插件必须区分遮阳帘与调光玻璃,执行前检查平台能力和防夹等安全条件,并只返回 `CommandPlan`。
+4
View File
@@ -0,0 +1,4 @@
# Roof Tests
覆盖位置方向、百分比边界、平台能力缺失、防夹/车速/温度互锁、校准缺失、执行超时和回读不一致。
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+8
View File
@@ -0,0 +1,8 @@
domain: seat
primary_owner: seat_owner
reviewers:
- integration_owner
- can_owner
paths:
- domains/seat/**
+25
View File
@@ -0,0 +1,25 @@
# Seat Domain
负责座椅单领域能力。通风和加热基于根目录 `座椅部分功能CAN矩阵表.xlsx` 建立可审计的归一化矩阵;位置电机使用单独的虚构矩阵,仅验证模型理解、配置生成、CAN 编译和反馈闭环。
## 首个交付目标
- 支持显式指令以及“主驾有点热,有点闷”等舒适反馈。
- GPT 在同一 `conversation_id` 下获取当前虚拟执行状态和前序成功动作,自行解析跟进、因果和作用域。
- GPT 返回领域无关的通用能力请求;座椅适配器只校验并转换已注册能力,不用本地舒适或位置词条覆盖模型结果。
- 主副驾分别支持水平、高度、坐垫俯仰、靠背、腿托升降和腿托伸缩六个虚构电机轴,会话状态保留每轴的 `0-100%` 绝对位置。
- 混合请求中可执行的座椅动作独立 dry-run,未接入的其他域保留为 `deferred_requests`,不会阻断座椅子请求。
- 生成只含 `domain: seat` 的 `FunctionConfig`。
- 1–3 档目标进入 `FunctionConfig` 和会话状态;`0x42A` 仍只编译开关,档位由 `0x4FA` dry-run 模拟。
- 使用模拟 CAN Gateway 测试正常执行、冲突、缺失能力和回读不一致。
## 待负责人补齐
- 0x42A 的正式报文名称、发送节点、周期、DLC、字节序、counter/checksum 和总线所有权。
- 档位控制信号;当前“设置三档”只是认知目标和虚拟回读,不得视为已产生实车档位命令。
- 行车状态、乘员状态和防夹等安全互锁。
- DBC message/signal 以及命令保持、释放和真实回读策略。
`can/seat-thermal-matrix.yaml` 和 `can/seat-motion-virtual-matrix.yaml` 中 `hardware_execution_allowed: false` 是强制边界。后者的帧号、信号布局、时序和反馈行为全部为虚构,当前闭环只能 dry-run。
真实 T2G 台架使用独立的 `signal-mapping.hardware.yaml`、`seat-thermal-hardware-matrix.yaml` 和 `seat-motion-hardware-matrix.yaml`,不会解除上述虚拟矩阵的边界。仓库内 `firmware/t2g/Seat_Controller_demo` 是唯一硬件固件接口,真实路径只接受它的 `combined` profile。当前主驾与副驾都是 `t2g_primary_seat` 的逻辑别名:两种指令使用同一组实体电机、加热和通风,成功反馈会同步到两侧会话状态。完整接线、自动烧录、授权和反馈要求见 [T2G 座椅真实硬件台架接入](../../docs/workflows/t2g-seat-hardware-bench.md)。
+71
View File
@@ -0,0 +1,71 @@
version: 3.0.0
source:
project: Seat_Move_demo
workbook: Seat_Move_demo/Docs/outputs/seat_move_four_motor_20260724/座椅四电机移动功能CAN矩阵表.xlsx
test_report: Seat_Move_demo/Docs/TEST_REPORT_2026-07-24.md
verified_on: 2026-07-24
scope: t2g_driver_seat_four_motor_protocol
hardware_execution_allowed: true
bit_numbering: lsb0
byte_order: intel_little_endian
limitations:
- Motor 0 through 2 were hardware verified; Motor 3 is disabled until it is wired and calibrated.
- Cushion tilt has no actuator in Seat_Move_demo and is never eligible for hardware transmission.
- Mechanical direction labels still require a final visual sign-off on the assembled seat.
messages:
- name: T2G_Seat_Move_Command_300
frame_id: 0x300
role: command
minimum_payload_bytes: 8
hardware_send_allowed: true
signals:
- {name: Opcode, start_bit: 0, bit_length: 8}
- {name: Motor_Selector, start_bit: 8, bit_length: 2}
- {name: Direction, start_bit: 10, bit_length: 2}
- {name: Protocol_Version, start_bit: 12, bit_length: 2}
- {name: Sequence, start_bit: 16, bit_length: 16}
- {name: Param_A, start_bit: 32, bit_length: 16}
- {name: Param_B, start_bit: 48, bit_length: 16}
- name: T2G_Seat_Move_System_Status_301
frame_id: 0x301
role: feedback
minimum_payload_bytes: 8
signals:
- {name: Ack, start_bit: 0, bit_length: 3}
- {name: Reject_Reason, start_bit: 3, bit_length: 3}
- {name: Shutdown_Inhibit, start_bit: 6, bit_length: 1}
- {name: Safety_Inhibit, start_bit: 7, bit_length: 1}
- {name: Last_Opcode, start_bit: 8, bit_length: 4}
- {name: Active_Mask, start_bit: 12, bit_length: 4}
- {name: Last_Sequence, start_bit: 16, bit_length: 16}
- {name: Retry_Mask, start_bit: 32, bit_length: 4}
- {name: Record_Clean, start_bit: 36, bit_length: 1}
- {name: Watchdog_Reset, start_bit: 37, bit_length: 1}
- {name: Record_Valid, start_bit: 38, bit_length: 1}
- {name: Safety_Fault, start_bit: 40, bit_length: 8}
- {name: Config_Sequence, start_bit: 48, bit_length: 16}
- name: T2G_Seat_Move_Motor_Status_302
frame_id: 0x302
role: feedback
minimum_payload_bytes: 8
signals:
- {name: Motor_Sub_ID, start_bit: 0, bit_length: 8}
- {name: Task_State, start_bit: 8, bit_length: 4}
- {name: Result, start_bit: 12, bit_length: 4}
- {name: Direction, start_bit: 16, bit_length: 2}
- {name: Active, start_bit: 18, bit_length: 1}
- {name: Retried, start_bit: 19, bit_length: 1}
- {name: Position_Valid, start_bit: 20, bit_length: 1}
- {name: Motor_Sequence, start_bit: 24, bit_length: 16}
- {name: Value_X100, start_bit: 40, bit_length: 16}
- {name: Duty_Percent, start_bit: 56, bit_length: 8}
- name: T2G_Seat_Move_Diagnostic_303
frame_id: 0x303
role: feedback
minimum_payload_bytes: 8
signals:
- {name: Motor_Sub_ID, start_bit: 0, bit_length: 8}
- {name: Last_Motor_Event, start_bit: 8, bit_length: 4}
- {name: Hall_Count, start_bit: 16, bit_length: 16}
- {name: Max_Hall_Count, start_bit: 32, bit_length: 16}
- {name: Max_Speed_Pps_X100, start_bit: 48, bit_length: 16}
+55
View File
@@ -0,0 +1,55 @@
version: 0.1.0
source:
kind: synthetic_demo
file: seat-motion-virtual-matrix.yaml
designed_on: 2026-07-23
scope: driver_and_front_passenger_position_motors
hardware_execution_allowed: false
synthetic: true
bit_numbering: lsb0
byte_order: intel_little_endian
limitations:
- All frame IDs, signal layouts, timing and feedback behavior in this file are fictional.
- Values are normalized positions, not physical millimeters or degrees.
- No vehicle-state, occupant, anti-pinch, current-sensing or collision interlock is implemented.
- These frames must never be sent to a vehicle, bench ECU or HIL network.
messages:
- name: Driver_Seat_Motion_Command_550
frame_id: 0x550
role: synthetic_command
minimum_payload_bytes: 8
hardware_send_allowed: false
signals: &motion_command_signals
- {name: Longitudinal_Target_Pct, description: "Fore-aft absolute target", start_bit: 0, bit_length: 7}
- {name: Cushion_Tilt_Target_Pct, description: "Cushion pitch absolute target", start_bit: 7, bit_length: 7}
- {name: Backrest_Recline_Target_Pct, description: "Backrest recline absolute target", start_bit: 14, bit_length: 7}
- {name: Legrest_Height_Target_Pct, description: "Leg-rest lift absolute target", start_bit: 21, bit_length: 7}
- {name: Legrest_Extension_Target_Pct, description: "Leg-rest extension absolute target", start_bit: 28, bit_length: 7}
- {name: Motion_Enable_Mask, description: "One enable bit per position axis", start_bit: 35, bit_length: 5}
- {name: Command_Counter, description: "Synthetic per-plan command sequence", start_bit: 40, bit_length: 4}
- {name: Command_Mode, description: "0=no request, 1=absolute position", start_bit: 44, bit_length: 2}
- {name: Command_Checksum, description: "Synthetic additive checksum", start_bit: 48, bit_length: 8}
- name: Passenger_Seat_Motion_Command_551
frame_id: 0x551
role: synthetic_command
minimum_payload_bytes: 8
hardware_send_allowed: false
signals: *motion_command_signals
- name: Driver_Seat_Motion_Status_558
frame_id: 0x558
role: synthetic_feedback
minimum_payload_bytes: 8
signals: &motion_status_signals
- {name: Longitudinal_Actual_Pct, description: "Fore-aft measured position", start_bit: 0, bit_length: 7}
- {name: Cushion_Tilt_Actual_Pct, description: "Cushion pitch measured position", start_bit: 7, bit_length: 7}
- {name: Backrest_Recline_Actual_Pct, description: "Backrest recline measured position", start_bit: 14, bit_length: 7}
- {name: Legrest_Height_Actual_Pct, description: "Leg-rest lift measured position", start_bit: 21, bit_length: 7}
- {name: Legrest_Extension_Actual_Pct, description: "Leg-rest extension measured position", start_bit: 28, bit_length: 7}
- {name: Moving_Mask, description: "One moving-state bit per position axis", start_bit: 35, bit_length: 5}
- {name: Fault_Mask, description: "One synthetic motor-fault bit per position axis", start_bit: 40, bit_length: 5}
- {name: Position_Valid_Mask, description: "One valid-feedback bit per position axis", start_bit: 45, bit_length: 5}
- name: Passenger_Seat_Motion_Status_559
frame_id: 0x559
role: synthetic_feedback
minimum_payload_bytes: 8
signals: *motion_status_signals
+43
View File
@@ -0,0 +1,43 @@
version: 1.0.0
source:
projects:
- Seat_Backrest_Heater_demo
- Seat_Fan_demo
workbooks:
- 座椅加热功能CAN矩阵表.xlsx
- 座椅通风功能CAN矩阵表.xlsx
verified_on: 2026-07-23
scope: t2g_driver_seat_heating_and_ventilation
hardware_execution_allowed: true
bit_numbering: lsb0
byte_order: intel_little_endian
limitations:
- Only the driver-seat backrest heater and ventilation fan are implemented by the verified demos.
- The two demos are standalone firmware images; use one board per profile or a reviewed combined image.
- Product deployment still requires command timeout, E2E protection and calibrated thermal/current limits.
messages:
- name: T2G_Seat_Thermal_Control_29E
frame_id: 0x29E
role: command
minimum_payload_bytes: 8
hardware_send_allowed: true
signals:
- {name: Driver_Seat_Ventilation_On, start_bit: 0, bit_length: 2}
- {name: Driver_Seat_Ventilation_Mode, start_bit: 2, bit_length: 2}
- {name: Driver_Seat_Heating_On, start_bit: 4, bit_length: 2}
- {name: Driver_Seat_Ventilation_Gear, start_bit: 16, bit_length: 4}
- {name: Driver_Seat_Heating_Gear, start_bit: 20, bit_length: 4}
- name: T2G_Seat_Thermal_Mode_3CE
frame_id: 0x3CE
role: command
minimum_payload_bytes: 8
hardware_send_allowed: true
signals:
- {name: Driver_Seat_Thermal_Mode, start_bit: 4, bit_length: 2}
- name: T2G_Seat_Thermal_Status_4FA
frame_id: 0x4FA
role: feedback
minimum_payload_bytes: 8
signals:
- {name: Driver_Seat_Ventilation_Status, start_bit: 0, bit_length: 3}
- {name: Driver_Seat_Heating_Status, start_bit: 3, bit_length: 3}
+189
View File
@@ -0,0 +1,189 @@
version: 1.0.0
source:
file: ../../../座椅部分功能CAN矩阵表.xlsx
sheet: Sheet1
normalized_on: 2026-07-22
scope: driver_and_front_passenger_heating_ventilation
hardware_execution_allowed: false
bit_numbering: lsb0_inferred_from_start_bit_and_bit_columns
byte_order: intel_little_endian_inferred_not_confirmed
limitations:
- Message names are local aliases because the spreadsheet only provides frame IDs.
- Transmitter, receiver, cycle time, DLC, byte order, counter and checksum are absent.
- Payload lengths below are minimum lengths inferred from the highest occupied bit.
- Frame 0x42A is treated as a control candidate only for virtual execution.
- Gear signals are observable states; no gear command signal is identified.
messages:
- name: Seat_Thermal_Status_4FA
frame_id: 0x4FA
role: feedback
minimum_payload_bytes: 2
signals:
- name: Pilot_Seat_Vtilat_Stat_S
description: 主驾座椅通风工作状态
start_bit: 0
bit_length: 3
values:
0: invalid
1: "off"
2: low
3: high
4: level_3
- name: Pilot_Seat_Heat_Stat_S
description: 主驾座椅加热工作状态
start_bit: 3
bit_length: 3
values:
0: invalid
1: "off"
2: low
3: high
4: level_3
- name: Copilot_Seat_Vtilat_Stat_4FA_S
description: 副驾座椅通风工作状态
start_bit: 8
bit_length: 3
values:
0: invalid
1: "off"
2: low
3: high
4: level_3
- name: Copilot_Seat_Heat_Stat_4FA_S
description: 副驾座椅加热工作状态
start_bit: 11
bit_length: 3
values:
0: invalid
1: "off"
2: low
3: high
4: level_3
- name: Driver_Seat_Thermal_Status_29E
frame_id: 0x29E
role: feedback
minimum_payload_bytes: 3
signals:
- name: Driver_Seat_Ventilation_On_S
description: 主驾座椅通风开启状态
start_bit: 0
bit_length: 2
values: {0: invalid, 1: "on", 2: "off", 3: reserved}
- name: Driver_Seat_Ventilation_Control_Mode_S
description: 主驾座椅通风控制模式
start_bit: 2
bit_length: 2
values: {0: invalid, 1: manual, 2: automatic, 3: reserved}
- name: The_Driver_Seat_Heating_Is_On_S
description: 主驾座椅加热开启状态
start_bit: 4
bit_length: 2
values: {0: invalid, 1: "on", 2: "off", 3: reserved}
- name: Driver_Seat_Ventilation_Gear_S
description: 主驾座椅通风档位
start_bit: 16
bit_length: 4
values: {0: invalid, 1: level_1, 2: level_2, 3: level_3, 4: level_4, 5: level_5}
- name: Heated_Driver_Seat_S
description: 主驾座椅加热档位
start_bit: 20
bit_length: 4
values: {0: invalid, 1: level_1, 2: level_2, 3: level_3, 4: level_4, 5: level_5}
- name: Driver_Seat_Thermal_Mode_3CE
frame_id: 0x3CE
role: feedback
minimum_payload_bytes: 1
signals:
- name: Main_Driver_Seat_Ven_And_Hea_Control_Mode_S
description: 主驾座椅通风加热控制模式
start_bit: 4
bit_length: 2
values: {0: invalid, 1: manual, 2: automatic, 3: reserved}
- name: Passenger_Seat_Thermal_Status_3D3
frame_id: 0x3D3
role: feedback
minimum_payload_bytes: 3
signals:
- name: Front_Pass_Seat_Ventilation_On_S
description: 副驾座椅通风开启状态
start_bit: 0
bit_length: 2
values: {0: invalid, 1: "on", 2: "off", 3: reserved}
- name: Pas_Seat_Heating_On_S
description: 副驾座椅加热开启状态
start_bit: 2
bit_length: 2
values: {0: invalid, 1: "on", 2: "off", 3: reserved}
- name: Front_Pass_Seat_Ven_And_Hea_Control_Mode_S
description: 副驾座椅通风加热控制模式
start_bit: 4
bit_length: 2
values: {0: invalid, 1: manual, 2: automatic, 3: reserved}
- name: Passenger_Seat_Ventilation_Gear_3D3_S
description: 副驾座椅通风档位
start_bit: 12
bit_length: 4
values: {0: invalid, 1: level_1, 2: level_2, 3: level_3, 4: level_4, 5: level_5}
- name: Heated_Passenger_Seat_3D3_S
description: 副驾座椅加热档位
start_bit: 16
bit_length: 4
values: {0: invalid, 1: level_1, 2: level_2, 3: level_3, 4: level_4, 5: level_5}
- name: Seat_Thermal_Configuration_2AA_01
frame_id: 0x2AA
sub_id: 0x01
role: configuration_status
minimum_payload_bytes: 3
signals:
- name: Copilot_Seat_Three_Speed_Ventilat_Config_S
description: 副驾座椅通风配置
start_bit: 8
bit_length: 3
values: {0: invalid, 1: level_1_config, 2: level_2_config, 3: level_3_config, 4: level_5_split_config}
- name: Copilot_Seat_Three_Speed_Heat_Config_S
description: 副驾座椅加热配置
start_bit: 11
bit_length: 3
values: {0: invalid, 1: level_1_config, 2: level_2_config, 3: level_3_config, 4: level_5_split_config}
- name: Driver_Seat_Three_Speed_Ventilat_Config_S
description: 主驾座椅通风配置
start_bit: 16
bit_length: 3
values: {0: invalid, 1: level_1_config, 2: level_2_config, 3: level_3_config, 4: level_5_split_config}
- name: Driver_Seat_Three_Speed_Heat_Config_S
description: 主驾座椅加热配置
start_bit: 19
bit_length: 3
values: {0: invalid, 1: level_1_config, 2: level_2_config, 3: level_3_config, 4: level_5_split_config}
- name: Seat_Thermal_Control_42A
frame_id: 0x42A
role: command_candidate
minimum_payload_bytes: 2
hardware_send_allowed: false
signals:
- name: Driver_Seat_Ventilation_S
description: 主驾通风开启/关闭
start_bit: 0
bit_length: 2
values: {0: invalid, 1: "on", 2: "off", 3: reserved}
- name: Driver_Seat_Heating_S
description: 主驾加热开启/关闭
start_bit: 2
bit_length: 2
values: {0: invalid, 1: "on", 2: "off", 3: reserved}
- name: Front_Passenger_Seat_Ventilation_S
description: 副驾通风开启/关闭
start_bit: 12
bit_length: 2
values: {0: invalid, 1: "on", 2: "off", 3: reserved}
- name: Front_Passenger_Seat_Heating_S
description: 副驾加热开启/关闭
start_bit: 14
bit_length: 2
values: {0: invalid, 1: "on", 2: "off", 3: reserved}
+150
View File
@@ -0,0 +1,150 @@
apiVersion: cognitive-agent/v1alpha1
kind: SignalMapping
metadata:
id: seat-t2g-bench-hardware-mapping
name: Verified T2G seat bench mapping
version: 1.1.0
description: Mapping implemented by the unified Seat_Controller_demo IAR firmware.
spec:
domain: seat
platform: t2g_seat_bench
dbc_ref: ../../../platforms/t2g_seat_bench/can/dbc/seat.dbc
matrix_ref: seat-thermal-hardware-matrix.yaml
status: hil_verified
mappings:
- capability: seat.set_ventilation
parameter: state
target_zone: front_left
physical_target_alias: t2g_primary_seat
compiler_handler: compile_t2g_thermal
bus: seat_can
message: T2G_Seat_Thermal_Control_29E
signals: [Driver_Seat_Ventilation_On, Driver_Seat_Ventilation_Mode, Driver_Seat_Ventilation_Gear]
feedback_message: T2G_Seat_Thermal_Status_4FA
feedback_signal: Driver_Seat_Ventilation_Status
- capability: seat.set_heating
parameter: state
target_zone: front_left
physical_target_alias: t2g_primary_seat
compiler_handler: compile_t2g_thermal
bus: seat_can
message: T2G_Seat_Thermal_Control_29E
signals: [Driver_Seat_Heating_On, Driver_Seat_Heating_Gear]
feedback_message: T2G_Seat_Thermal_Status_4FA
feedback_signal: Driver_Seat_Heating_Status
- capability: seat.set_longitudinal_position
parameter: position_pct
target_zone: front_left
physical_target_alias: t2g_primary_seat
compiler_handler: compile_t2g_absolute_position
bus: seat_can
message: T2G_Seat_Move_Command_300
signal: Param_A
feedback_message: T2G_Seat_Move_Motor_Status_302
feedback_signal: Value_X100
calibration_ref: ../../../platforms/t2g_seat_bench/calibration/seat.yaml
unit: percent
- capability: seat.set_backrest_recline
parameter: position_pct
target_zone: front_left
physical_target_alias: t2g_primary_seat
compiler_handler: compile_t2g_absolute_position
bus: seat_can
message: T2G_Seat_Move_Command_300
signal: Param_A
feedback_message: T2G_Seat_Move_Motor_Status_302
feedback_signal: Value_X100
calibration_ref: ../../../platforms/t2g_seat_bench/calibration/seat.yaml
unit: percent
- capability: seat.set_legrest_height
parameter: position_pct
target_zone: front_left
physical_target_alias: t2g_primary_seat
compiler_handler: compile_t2g_absolute_position
bus: seat_can
message: T2G_Seat_Move_Command_300
signal: Param_A
feedback_message: T2G_Seat_Move_Motor_Status_302
feedback_signal: Value_X100
calibration_ref: ../../../platforms/t2g_seat_bench/calibration/seat.yaml
unit: percent
- capability: seat.set_legrest_extension
parameter: position_pct
target_zone: front_left
physical_target_alias: t2g_primary_seat
compiler_handler: compile_t2g_absolute_position
bus: seat_can
message: T2G_Seat_Move_Command_300
signal: Param_A
feedback_message: T2G_Seat_Move_Motor_Status_302
feedback_signal: Value_X100
calibration_ref: ../../../platforms/t2g_seat_bench/calibration/seat.yaml
unit: percent
- capability: seat.set_ventilation
parameter: state
target_zone: front_right
physical_target_alias: t2g_primary_seat
compiler_handler: compile_t2g_thermal
bus: seat_can
message: T2G_Seat_Thermal_Control_29E
signals: [Driver_Seat_Ventilation_On, Driver_Seat_Ventilation_Mode, Driver_Seat_Ventilation_Gear]
feedback_message: T2G_Seat_Thermal_Status_4FA
feedback_signal: Driver_Seat_Ventilation_Status
- capability: seat.set_heating
parameter: state
target_zone: front_right
physical_target_alias: t2g_primary_seat
compiler_handler: compile_t2g_thermal
bus: seat_can
message: T2G_Seat_Thermal_Control_29E
signals: [Driver_Seat_Heating_On, Driver_Seat_Heating_Gear]
feedback_message: T2G_Seat_Thermal_Status_4FA
feedback_signal: Driver_Seat_Heating_Status
- capability: seat.set_longitudinal_position
parameter: position_pct
target_zone: front_right
physical_target_alias: t2g_primary_seat
compiler_handler: compile_t2g_absolute_position
bus: seat_can
message: T2G_Seat_Move_Command_300
signal: Param_A
feedback_message: T2G_Seat_Move_Motor_Status_302
feedback_signal: Value_X100
calibration_ref: ../../../platforms/t2g_seat_bench/calibration/seat.yaml
unit: percent
- capability: seat.set_backrest_recline
parameter: position_pct
target_zone: front_right
physical_target_alias: t2g_primary_seat
compiler_handler: compile_t2g_absolute_position
bus: seat_can
message: T2G_Seat_Move_Command_300
signal: Param_A
feedback_message: T2G_Seat_Move_Motor_Status_302
feedback_signal: Value_X100
calibration_ref: ../../../platforms/t2g_seat_bench/calibration/seat.yaml
unit: percent
- capability: seat.set_legrest_height
parameter: position_pct
target_zone: front_right
physical_target_alias: t2g_primary_seat
compiler_handler: compile_t2g_absolute_position
bus: seat_can
message: T2G_Seat_Move_Command_300
signal: Param_A
feedback_message: T2G_Seat_Move_Motor_Status_302
feedback_signal: Value_X100
calibration_ref: ../../../platforms/t2g_seat_bench/calibration/seat.yaml
unit: percent
- capability: seat.set_legrest_extension
parameter: position_pct
target_zone: front_right
physical_target_alias: t2g_primary_seat
compiler_handler: compile_t2g_absolute_position
bus: seat_can
message: T2G_Seat_Move_Command_300
signal: Param_A
feedback_message: T2G_Seat_Move_Motor_Status_302
feedback_signal: Value_X100
calibration_ref: ../../../platforms/t2g_seat_bench/calibration/seat.yaml
unit: percent
+132
View File
@@ -0,0 +1,132 @@
apiVersion: cognitive-agent/v1alpha1
kind: SignalMapping
metadata:
id: seat-reference-vehicle-mapping
name: Seat mapping for reference vehicle
version: 1.1.0
description: Supplied thermal matrix plus explicitly synthetic motion mappings; virtual execution only.
spec:
domain: seat
platform: reference_vehicle
dbc_ref: ../../../platforms/reference_vehicle/can/dbc/vehicle.dbc
matrix_ref: seat-thermal-matrix.yaml
status: draft
mappings:
- capability: seat.set_ventilation
parameter: state
target_zone: front_left
compiler_handler: compile_binary_control
bus: body_can
message: Seat_Thermal_Control_42A
signal: Driver_Seat_Ventilation_S
- capability: seat.set_heating
parameter: state
target_zone: front_left
compiler_handler: compile_binary_control
bus: body_can
message: Seat_Thermal_Control_42A
signal: Driver_Seat_Heating_S
- capability: seat.set_ventilation
parameter: state
target_zone: front_right
compiler_handler: compile_binary_control
bus: body_can
message: Seat_Thermal_Control_42A
signal: Front_Passenger_Seat_Ventilation_S
- capability: seat.set_heating
parameter: state
target_zone: front_right
compiler_handler: compile_binary_control
bus: body_can
message: Seat_Thermal_Control_42A
signal: Front_Passenger_Seat_Heating_S
- capability: seat.set_longitudinal_position
parameter: position_pct
target_zone: front_left
compiler_handler: compile_position_control
bus: body_can
message: Driver_Seat_Motion_Command_550
signal: Longitudinal_Target_Pct
calibration_ref: seat-motion-virtual-matrix.yaml
unit: percent
- capability: seat.set_cushion_tilt
parameter: position_pct
target_zone: front_left
compiler_handler: compile_position_control
bus: body_can
message: Driver_Seat_Motion_Command_550
signal: Cushion_Tilt_Target_Pct
calibration_ref: seat-motion-virtual-matrix.yaml
unit: percent
- capability: seat.set_backrest_recline
parameter: position_pct
target_zone: front_left
compiler_handler: compile_position_control
bus: body_can
message: Driver_Seat_Motion_Command_550
signal: Backrest_Recline_Target_Pct
calibration_ref: seat-motion-virtual-matrix.yaml
unit: percent
- capability: seat.set_legrest_height
parameter: position_pct
target_zone: front_left
compiler_handler: compile_position_control
bus: body_can
message: Driver_Seat_Motion_Command_550
signal: Legrest_Height_Target_Pct
calibration_ref: seat-motion-virtual-matrix.yaml
unit: percent
- capability: seat.set_legrest_extension
parameter: position_pct
target_zone: front_left
compiler_handler: compile_position_control
bus: body_can
message: Driver_Seat_Motion_Command_550
signal: Legrest_Extension_Target_Pct
calibration_ref: seat-motion-virtual-matrix.yaml
unit: percent
- capability: seat.set_longitudinal_position
parameter: position_pct
target_zone: front_right
compiler_handler: compile_position_control
bus: body_can
message: Passenger_Seat_Motion_Command_551
signal: Longitudinal_Target_Pct
calibration_ref: seat-motion-virtual-matrix.yaml
unit: percent
- capability: seat.set_cushion_tilt
parameter: position_pct
target_zone: front_right
compiler_handler: compile_position_control
bus: body_can
message: Passenger_Seat_Motion_Command_551
signal: Cushion_Tilt_Target_Pct
calibration_ref: seat-motion-virtual-matrix.yaml
unit: percent
- capability: seat.set_backrest_recline
parameter: position_pct
target_zone: front_right
compiler_handler: compile_position_control
bus: body_can
message: Passenger_Seat_Motion_Command_551
signal: Backrest_Recline_Target_Pct
calibration_ref: seat-motion-virtual-matrix.yaml
unit: percent
- capability: seat.set_legrest_height
parameter: position_pct
target_zone: front_right
compiler_handler: compile_position_control
bus: body_can
message: Passenger_Seat_Motion_Command_551
signal: Legrest_Height_Target_Pct
calibration_ref: seat-motion-virtual-matrix.yaml
unit: percent
- capability: seat.set_legrest_extension
parameter: position_pct
target_zone: front_right
compiler_handler: compile_position_control
bus: body_can
message: Passenger_Seat_Motion_Command_551
signal: Legrest_Extension_Target_Pct
calibration_ref: seat-motion-virtual-matrix.yaml
unit: percent
+211
View File
@@ -0,0 +1,211 @@
apiVersion: cognitive-agent/v1alpha1
kind: CapabilityCatalog
metadata:
id: seat-capabilities
name: Seat capabilities
version: 1.2.0
spec:
domain: seat
capabilities:
- name: seat.set_ventilation
description: >-
Move air through one seat surface to reduce local occupant heat and moisture; this does not
control general cabin airflow. Turn it on or off for the driver or front passenger seat.
target_devices:
- seat
target_scopes:
- driver
- passenger
parameters:
- name: state
type: string
required: true
description: Requested binary ventilation state.
allowed_values:
- "on"
- "off"
- name: level
type: integer
required: false
description: Desired 1-3 comfort level; simulated until a level command signal is supplied.
allowed_values:
- 1
- 2
- 3
risk_level: medium
confirmation_policy: on_ambiguity
- name: seat.set_heating
description: >-
Warm one seat surface for local occupant thermal comfort. Turn it on or off for the driver
or front passenger seat.
target_devices:
- seat
target_scopes:
- driver
- passenger
parameters:
- name: state
type: string
required: true
description: Requested binary heating state.
allowed_values:
- "on"
- "off"
- name: level
type: integer
required: false
description: Desired 1-3 comfort level; simulated until a level command signal is supplied.
allowed_values:
- 1
- 2
- 3
risk_level: medium
confirmation_policy: on_ambiguity
- name: seat.set_longitudinal_position
description: >-
Set one front seat's fore-aft rail position. position_pct is an absolute target: 0 is the
rearmost mechanical limit and 100 is the forwardmost limit. Resolve relative requests such
as "move back a little" against the current position supplied in runtime state.
target_devices:
- seat_longitudinal_motor
target_scopes:
- driver
- passenger
parameters:
- name: position_pct
type: integer
unit: percent
required: true
minimum: 0
maximum: 100
description: Absolute normalized fore-aft target.
risk_level: high
confirmation_policy: on_ambiguity
- name: seat.set_cushion_tilt
description: >-
Set the seat-cushion pitch for one front seat. position_pct is absolute: 0 places the front
edge at its lowest limit and 100 raises the front edge to its highest tilt limit.
target_devices:
- seat_cushion_tilt_motor
target_scopes:
- driver
- passenger
parameters:
- name: position_pct
type: integer
unit: percent
required: true
minimum: 0
maximum: 100
description: Absolute normalized cushion front-edge height target.
risk_level: high
confirmation_policy: on_ambiguity
- name: seat.set_backrest_recline
description: >-
Set the backrest recline of one front seat. Use either position_pct or angle_deg.
position_pct is absolute: 0 is the most upright limit and 100 is the most reclined limit.
angle_deg is the included angle between the seat-cushion surface and backrest centreline,
from 95 to 140 degrees. "Backrest backward" means a larger value.
target_devices:
- seat_backrest_motor
target_scopes:
- driver
- passenger
parameters:
- name: position_pct
type: integer
unit: percent
required: false
minimum: 0
maximum: 100
description: Absolute normalized backrest-recline target.
- name: angle_deg
type: number
unit: degree
required: false
minimum: 95
maximum: 140
description: Exact included angle between the cushion surface and backrest centreline.
risk_level: high
confirmation_policy: on_ambiguity
- name: seat.set_legrest_height
description: >-
Raise or lower one front seat's powered leg rest. Use either position_pct or angle_deg.
position_pct is absolute: 0 is fully lowered and 100 is fully raised. angle_deg is the
included angle between the cushion surface and leg-rest surface, from 95 to 160 degrees.
This axis does not change leg-rest extension.
target_devices:
- seat_legrest_lift_motor
target_scopes:
- driver
- passenger
parameters:
- name: position_pct
type: integer
unit: percent
required: false
minimum: 0
maximum: 100
description: Absolute normalized leg-rest lift target.
- name: angle_deg
type: number
unit: degree
required: false
minimum: 95
maximum: 160
description: Exact included angle between the cushion surface and leg-rest surface.
risk_level: high
confirmation_policy: on_ambiguity
- name: seat.set_legrest_extension
description: >-
Extend or retract one front seat's powered leg rest. position_pct is absolute: 0 is fully
retracted and 100 is fully extended. This axis does not change leg-rest height.
target_devices:
- seat_legrest_extension_motor
target_scopes:
- driver
- passenger
parameters:
- name: position_pct
type: integer
unit: percent
required: true
minimum: 0
maximum: 100
description: Absolute normalized leg-rest extension target.
risk_level: high
confirmation_policy: on_ambiguity
- name: seat.save_posture_preset
description: >-
Save the current five-axis posture of one front seat as a conversation-owned named preset.
This is a control-plane operation and does not emit a CAN command. Use only when the user
explicitly asks to save, remember or overwrite the current posture.
target_devices:
- seat_preset_store
target_scopes:
- driver
- passenger
parameters:
- name: preset_name
type: string
required: true
description: User-visible name of the posture preset to create or overwrite.
risk_level: low
confirmation_policy: on_ambiguity
- name: seat.apply_posture_preset
description: >-
Apply a previously saved five-axis posture preset to one front seat. The runtime expands
the stored FunctionPreset into the registered motion capabilities before CAN compilation.
Use this capability only when the named preset exists in runtime conversation context.
target_devices:
- seat_preset_store
target_scopes:
- driver
- passenger
parameters:
- name: preset_name
type: string
required: true
description: Exact user-visible name of an existing posture preset.
risk_level: high
confirmation_policy: on_ambiguity
+7
View File
@@ -0,0 +1,7 @@
# Planned Seat Capabilities
零重力姿态和车型级自定义角度仍不属于实车可执行交付。现有 Excel 只覆盖主/副驾通风和加热相关信号,没有滑轨、靠背、坐垫或腿托控制信号,也没有运动学标定。
当前已使用 `seat-motion-virtual-matrix.yaml` 提供五轴 `0-100%` 虚构位置能力,用于模型理解和端到端 dry-run 验证。靠背/坐垫 `95–140°` 和坐垫/腿托 `95–160°` 采用显式 synthetic 线性标定,仅用于验证精确夹角语义、配置生成和孪生展示。该文件不是 DBC,不提供真实物理毫米/角度转换,也不具备防夹、电流、占位、车速或挡位互锁。
当前支持把某一会话中指定座位的五轴 dry-run 状态保存为命名 `FunctionPreset` 并再次调用;这是用户态控制面数据,不是车型出厂零重力标定。获得完整姿态 CAN 矩阵、坐标系、安全互锁和回读定义后,再交付硬件可执行的 `seat.set_posture` 及官方零重力 preset。
+24
View File
@@ -0,0 +1,24 @@
apiVersion: cognitive-agent/v1alpha1
kind: FunctionPreset
metadata:
id: seat-driver-ventilation
name: Driver seat ventilation
version: 1.0.0
description: Binary ventilation command supported by the partial CAN matrix.
spec:
domain: seat
operations:
- id: driver_ventilation_on
capability: seat.set_ventilation
target:
device: seat
zone: front_left
instance: driver
parameters:
state: "on"
timeout_ms: 3000
failure_policy: abort
locked_parameters: []
applicability:
platform: reference_vehicle
+24
View File
@@ -0,0 +1,24 @@
apiVersion: cognitive-agent/v1alpha1
kind: FunctionPreset
metadata:
id: seat-passenger-heating
name: Front passenger seat heating
version: 1.0.0
description: Binary heating command supported by the partial CAN matrix.
spec:
domain: seat
operations:
- id: passenger_heating_on
capability: seat.set_heating
target:
device: seat
zone: front_right
instance: passenger
parameters:
state: "on"
timeout_ms: 3000
failure_policy: abort
locked_parameters: []
applicability:
platform: reference_vehicle
+16
View File
@@ -0,0 +1,16 @@
apiVersion: cognitive-agent/v1alpha1
kind: DomainManifest
metadata:
id: seat-domain-plugin
name: Seat domain plugin
version: 1.1.0
spec:
domain: seat
entrypoint: seat_domain.plugin:create_plugin
function_schema: schemas/function-parameters.schema.json
capabilities: config/capabilities.yaml
signal_mapping: can/signal-mapping.yaml
supported_platforms:
- reference_vehicle
- t2g_seat_bench
telemetry_required: true
+75
View File
@@ -0,0 +1,75 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://cognitive-agent.local/domains/seat/function-parameters.schema.json",
"title": "SeatThermalFunctionParameters",
"type": "object",
"properties": {
"spec": {
"type": "object",
"properties": {
"domain": {"const": "seat"},
"operations": {
"type": "array",
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"capability": {"const": "seat.set_ventilation"},
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["state"],
"properties": {
"state": {"type": "string", "enum": ["on", "off"]},
"level": {"type": "integer", "minimum": 1, "maximum": 3}
}
}
}
},
{
"type": "object",
"properties": {
"capability": {"const": "seat.set_heating"},
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["state"],
"properties": {
"state": {"type": "string", "enum": ["on", "off"]},
"level": {"type": "integer", "minimum": 1, "maximum": 3}
}
}
}
},
{
"type": "object",
"properties": {
"capability": {
"type": "string",
"enum": [
"seat.set_longitudinal_position",
"seat.set_cushion_tilt",
"seat.set_backrest_recline",
"seat.set_legrest_height",
"seat.set_legrest_extension"
]
},
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["position_pct"],
"properties": {
"position_pct": {"type": "integer", "minimum": 0, "maximum": 100},
"angle_deg": {"type": "number", "minimum": 95, "maximum": 160}
}
}
}
}
]
}
}
}
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# Seat Plugin Source
当前实现包含本地意图解析、配置生成、领域校验、确定性 Compiler、矩阵位编码和虚拟回读 Executor。Compiler 只返回 `CommandPlan`,虚拟 Executor 不打开 CAN socket。获得正式 DBC、发送属性、安全输入和审批前不得增加硬件发送路径。
+25
View File
@@ -0,0 +1,25 @@
"""Seat thermal domain plugin."""
from .compiler import SeatCompiler
from .executor import VirtualSeatExecutor
from .generator import build_function_config, build_function_preset, build_intent_ir
from .hardware_compiler import T2GSeatCompiler
from .hardware_executor import HardwareExecutionError, HardwareSeatExecutor
from .intent import parse_local_intent, resolve_contextual_intent
from .matrix import SeatCanMatrix
from .validator import validate_semantics
__all__ = [
"SeatCanMatrix",
"SeatCompiler",
"T2GSeatCompiler",
"HardwareExecutionError",
"HardwareSeatExecutor",
"VirtualSeatExecutor",
"build_function_config",
"build_function_preset",
"build_intent_ir",
"parse_local_intent",
"resolve_contextual_intent",
"validate_semantics",
]
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+133
View File
@@ -0,0 +1,133 @@
"""Deterministically compile seat FunctionConfig into a CommandPlan."""
from __future__ import annotations
import hashlib
import json
from pathlib import Path
from typing import Any
import yaml
STATE_VALUES = {"on": 1, "off": 2}
MOTION_AXIS_BITS = {
"seat.set_longitudinal_position": 0,
"seat.set_cushion_tilt": 1,
"seat.set_backrest_recline": 2,
"seat.set_legrest_height": 3,
"seat.set_legrest_extension": 4,
}
MOTION_STATUS_MESSAGES = {
"front_left": "Driver_Seat_Motion_Status_558",
"front_right": "Passenger_Seat_Motion_Status_559",
}
class SeatCompiler:
version = "1.1.0"
def __init__(self, mapping_path: Path):
self.mapping = yaml.safe_load(mapping_path.read_text(encoding="utf-8"))
self.mapping_version = self.mapping["metadata"]["version"]
def _mapping_for(self, capability: str, zone: str) -> dict[str, Any]:
for mapping in self.mapping["spec"]["mappings"]:
if mapping["capability"] == capability and mapping.get("target_zone") == zone:
return mapping
raise ValueError(f"No signal mapping for capability={capability}, zone={zone}")
def compile(self, function_config: dict[str, Any]) -> dict[str, Any]:
thermal_groups: dict[tuple[str, str], dict[str, Any]] = {}
motion_commands: list[dict[str, Any]] = []
motion_index = 0
for operation in function_config["spec"]["operations"]:
mapping = self._mapping_for(operation["capability"], operation["target"]["zone"])
handler = mapping["compiler_handler"]
if handler == "compile_binary_control":
key = (mapping["bus"], mapping["message"])
group = thermal_groups.setdefault(key, {"signals": {}, "operation_ids": []})
group["signals"][mapping["signal"]] = STATE_VALUES[
operation["parameters"]["state"]
]
group["operation_ids"].append(operation["id"])
continue
if handler != "compile_position_control":
raise ValueError(f"Unsupported compiler handler: {handler}")
target = int(operation["parameters"]["position_pct"])
axis_bit = MOTION_AXIS_BITS[operation["capability"]]
enable_mask = 1 << axis_bit
counter = motion_index % 16
checksum = (target + enable_mask + counter + 1) & 0xFF
feedback_signal = mapping["signal"].replace("_Target_", "_Actual_")
motion_commands.append(
{
"id": f"move_{operation['id']}",
"operation_ref": operation["id"],
"bus": mapping["bus"],
"message": mapping["message"],
"signals": dict(
sorted(
{
mapping["signal"]: target,
"Motion_Enable_Mask": enable_mask,
"Command_Counter": counter,
"Command_Mode": 1,
"Command_Checksum": checksum,
}.items()
)
),
"send_offset_ms": motion_index * 50,
"repeat": {"count": 3, "period_ms": 20},
"completion": {
"message": MOTION_STATUS_MESSAGES[operation["target"]["zone"]],
"signal": feedback_signal,
"operator": "within",
"value": target,
"tolerance": 1,
"timeout_ms": int(operation.get("timeout_ms", 8000)),
},
}
)
motion_index += 1
canonical = json.dumps(function_config, ensure_ascii=False, sort_keys=True).encode("utf-8")
config_hash = hashlib.sha256(canonical).hexdigest()
metadata = function_config["metadata"]
commands = [
{
"id": "seat_thermal_control" if index == 0 else f"seat_thermal_control_{index}",
"operation_ref": ",".join(group["operation_ids"]),
"bus": bus,
"message": message,
"signals": dict(sorted(group["signals"].items())),
"send_offset_ms": 0,
}
for index, ((bus, message), group) in enumerate(thermal_groups.items())
]
commands.extend(motion_commands)
return {
"apiVersion": "cognitive-agent/v1alpha1",
"kind": "CommandPlan",
"metadata": {
"id": f"plan-{metadata['request_id']}",
"name": "Seat CAN command plan",
"version": "1.0.0",
"request_id": metadata["request_id"],
"labels": {"execution_scope": "virtual_only"},
},
"spec": {
"domain": "seat",
"platform": "reference_vehicle",
"source": {
"config_id": metadata["id"],
"config_version": metadata["version"],
"config_sha256": config_hash,
"compiler_version": self.version,
"mapping_version": self.mapping_version,
},
"commands": commands,
},
}
+199
View File
@@ -0,0 +1,199 @@
"""Virtual CAN executor and feedback simulator for the partial matrix."""
from __future__ import annotations
from datetime import UTC, datetime
from typing import Any
from .matrix import SeatCanMatrix
COMMAND_TO_FEEDBACK = {
"Driver_Seat_Ventilation_S": "Pilot_Seat_Vtilat_Stat_S",
"Driver_Seat_Heating_S": "Pilot_Seat_Heat_Stat_S",
"Front_Passenger_Seat_Ventilation_S": "Copilot_Seat_Vtilat_Stat_4FA_S",
"Front_Passenger_Seat_Heating_S": "Copilot_Seat_Heat_Stat_4FA_S",
}
OPERATION_TO_COMMAND = {
("front_left", "seat.set_ventilation"): "Driver_Seat_Ventilation_S",
("front_left", "seat.set_heating"): "Driver_Seat_Heating_S",
("front_right", "seat.set_ventilation"): "Front_Passenger_Seat_Ventilation_S",
("front_right", "seat.set_heating"): "Front_Passenger_Seat_Heating_S",
}
MOTION_CAPABILITY_TO_AXIS = {
"seat.set_longitudinal_position": "longitudinal",
"seat.set_cushion_tilt": "cushion_tilt",
"seat.set_backrest_recline": "backrest_recline",
"seat.set_legrest_height": "legrest_height",
"seat.set_legrest_extension": "legrest_extension",
}
MOTION_AXIS_BITS = {
"longitudinal": 0,
"cushion_tilt": 1,
"backrest_recline": 2,
"legrest_height": 3,
"legrest_extension": 4,
}
def _now() -> str:
return datetime.now(UTC).isoformat().replace("+00:00", "Z")
class VirtualSeatExecutor:
def __init__(self, matrix: SeatCanMatrix):
self.matrix = matrix
def execute(
self,
command_plan: dict[str, Any],
function_config: dict[str, Any],
actuator_state: dict[str, Any] | None = None,
) -> dict[str, Any]:
started_at = _now()
operation_by_id = {
operation["id"]: operation
for operation in function_config["spec"]["operations"]
}
results = []
for command in command_plan["spec"]["commands"]:
operation_ids = command["operation_ref"].split(",")
operations = [operation_by_id[item] for item in operation_ids]
if command["message"] == "Seat_Thermal_Control_42A":
results.extend(self._execute_thermal(command, operations))
else:
if len(operations) != 1:
raise ValueError("A motion command must reference exactly one operation")
results.append(
self._execute_motion(command, operations[0], actuator_state or {})
)
request_id = function_config["metadata"]["request_id"]
return {
"apiVersion": "cognitive-agent/v1alpha1",
"kind": "ExecutionReport",
"metadata": {
"id": f"report-{request_id}",
"name": "Seat virtual execution report",
"version": "1.0.0",
"request_id": request_id,
"labels": {"simulation": "true"},
},
"spec": {
"mode": "dry_run",
"status": "succeeded",
"started_at": started_at,
"finished_at": _now(),
"results": results,
},
}
def _execute_thermal(
self, command: dict[str, Any], operations: list[dict[str, Any]]
) -> list[dict[str, Any]]:
command_frame = self.matrix.encode(command["message"], command["signals"])
operation_by_signal = {
OPERATION_TO_COMMAND[(operation["target"]["zone"], operation["capability"])]: operation
for operation in operations
}
feedback_values = {}
for signal, value in command["signals"].items():
operation = operation_by_signal[signal]
level = int(operation["parameters"].get("level", 1))
feedback_values[COMMAND_TO_FEEDBACK[signal]] = level + 1 if value == 1 else 1
feedback_frame = self.matrix.encode("Seat_Thermal_Status_4FA", feedback_values)
results = []
for operation in operations:
command_signal = OPERATION_TO_COMMAND[
(operation["target"]["zone"], operation["capability"])
]
feedback_signal = COMMAND_TO_FEEDBACK[command_signal]
state = operation["parameters"]["state"]
requested_level = operation["parameters"].get("level")
effective_level = (requested_level or 1) if state == "on" else 0
results.append(
{
"ref": operation["id"],
"domain": "seat",
"status": "succeeded",
"message": (
f"虚拟回读确认 {operation['target']['instance']} "
f"{operation['capability']}={state}, level={effective_level}"
),
"telemetry": {
"requested_state": state,
"requested_level": requested_level,
"effective_level": effective_level,
"level_command_simulated": requested_level is not None,
"command_signal": command_signal,
"command_value": command["signals"][command_signal],
"feedback_signal": feedback_signal,
"feedback_value": feedback_values[feedback_signal],
"command_frame": command_frame,
"feedback_frame": feedback_frame,
"simulated": True,
},
}
)
return results
def _execute_motion(
self,
command: dict[str, Any],
operation: dict[str, Any],
actuator_state: dict[str, Any],
) -> dict[str, Any]:
capability = operation["capability"]
axis = MOTION_CAPABILITY_TO_AXIS[capability]
instance = operation["target"]["instance"]
target = int(operation["parameters"]["position_pct"])
target_angle = operation["parameters"].get("angle_deg")
start = int(
actuator_state.get(instance, {})
.get("position", {})
.get(axis, {})
.get("position_pct", target)
)
command_frame = self.matrix.encode(command["message"], command["signals"])
axis_mask = 1 << MOTION_AXIS_BITS[axis]
completion = command["completion"]
feedback_values = {
completion["signal"]: target,
"Moving_Mask": 0,
"Fault_Mask": 0,
"Position_Valid_Mask": axis_mask,
}
feedback_frame = self.matrix.encode(completion["message"], feedback_values)
midpoint = round((start + target) / 2)
samples = list(dict.fromkeys([start, midpoint, target]))
return {
"ref": operation["id"],
"domain": "seat",
"status": "succeeded",
"message": (
f"虚构电机反馈确认 {instance} {axis} 从 {start}% 到达 {target}%"
),
"telemetry": {
"motion_axis": axis,
"start_position_pct": start,
"requested_position_pct": target,
"effective_position_pct": target,
"requested_angle_deg": target_angle,
"effective_angle_deg": target_angle,
"position_tolerance_pct": completion.get("tolerance", 0),
"motion_samples_pct": samples,
"motion_state": "stopped",
"moving_mask": 0,
"fault_mask": 0,
"position_valid_mask": axis_mask,
"command_signal": next(
name for name in command["signals"] if name.endswith("_Target_Pct")
),
"feedback_signal": completion["signal"],
"command_frame": command_frame,
"feedback_frame": feedback_frame,
"matrix_source": "synthetic_demo",
"simulated": True,
},
}
+277
View File
@@ -0,0 +1,277 @@
"""Generate public intent and function configuration documents."""
from __future__ import annotations
from datetime import UTC, datetime
from typing import Any
TARGETS = {
"driver": {"device": "seat", "zone": "front_left", "instance": "driver"},
"passenger": {"device": "seat", "zone": "front_right", "instance": "passenger"},
}
CAPABILITIES = {
"ventilation": "seat.set_ventilation",
"heating": "seat.set_heating",
}
MOTION_CAPABILITIES = {
"longitudinal": "seat.set_longitudinal_position",
"cushion_tilt": "seat.set_cushion_tilt",
"backrest_recline": "seat.set_backrest_recline",
"legrest_height": "seat.set_legrest_height",
"legrest_extension": "seat.set_legrest_extension",
}
def _now() -> str:
return datetime.now(UTC).isoformat().replace("+00:00", "Z")
def build_intent_ir(
parsed: dict[str, Any], utterance: str, request_id: str, provider: str
) -> dict[str, Any]:
requests = []
for action in parsed["actions"]:
if action.get("action_type") == "position":
parameters = {
"axis": action["axis"],
"position_pct": action["position_pct"],
}
if action.get("angle_deg") is not None:
parameters["angle_deg"] = action["angle_deg"]
requests.append(
{
"domain": "seat",
"goal": (
f"Set {action['target']} {action['axis']} position "
f"to {action['position_pct']} percent"
),
"target": TARGETS[action["target"]],
"parameters": parameters,
"source_span": utterance,
}
)
continue
parameters: dict[str, Any] = {
"function": action["function"],
"state": action["state"],
}
if action.get("requested_level") is not None:
parameters["requested_level"] = action["requested_level"]
requests.append(
{
"domain": "seat",
"goal": f"Set {action['target']} {action['function']} {action['state']}",
"target": TARGETS[action["target"]],
"parameters": parameters,
"source_span": utterance,
}
)
for preset_action in parsed.get("preset_actions", []):
requests.append(
{
"domain": "seat",
"goal": (
f"Save the current {preset_action['target']} posture as "
f"{preset_action['preset_name']}"
),
"target": TARGETS[preset_action["target"]],
"parameters": {
"capability": "seat.save_posture_preset",
"preset_name": preset_action["preset_name"],
},
"source_span": utterance,
}
)
for preset_event in parsed.get("preset_events", []):
requests.append(
{
"domain": "seat",
"goal": (
f"Apply saved posture {preset_event['preset_name']} to "
f"{preset_event['target']}"
),
"target": TARGETS[preset_event["target"]],
"parameters": {
"capability": "seat.apply_posture_preset",
"preset_name": preset_event["preset_name"],
},
"source_span": utterance,
}
)
for deferred in parsed.get("deferred_requests", []):
requests.append(
{
"domain": deferred["domain"],
"goal": f"{deferred['desired_change']} {deferred['capability']}",
"target": {
"device": deferred["domain"],
"zone": deferred["target_scope"],
},
"parameters": {
"capability": deferred["capability"],
"desired_change": deferred["desired_change"],
"requested_value": deferred.get("requested_value"),
"requested_parameters": deferred.get("parameters", {}),
"availability": "catalog_only",
},
"source_span": utterance,
}
)
detected_domains = parsed.get("detected_domains", ["seat"])
return {
"apiVersion": "cognitive-agent/v1alpha1",
"kind": "IntentIR",
"metadata": {
"id": f"intent-{request_id}",
"name": "Seat function intent",
"version": "1.0.0",
"request_id": request_id,
"generated_at": _now(),
"labels": {"provider": provider},
},
"spec": {
"utterance": utterance,
"language": "zh-CN",
"mode": "cross_domain" if len(detected_domains) > 1 else "single_domain",
"detected_domains": detected_domains,
"requests": requests,
"ambiguities": parsed["ambiguities"],
"confidence": parsed["confidence"],
"requires_confirmation": bool(parsed["ambiguities"]),
},
}
def build_function_config(
parsed: dict[str, Any], request_id: str, provider: str
) -> dict[str, Any] | None:
if not parsed["actions"]:
return None
operations = []
assumptions = [
"The supplied matrix is partial; this configuration is eligible for virtual execution only."
]
for index, action in enumerate(parsed["actions"], start=1):
if action.get("action_type") == "position":
operation_id = (
f"{action['target']}_{action['axis']}_{action['position_pct']}pct"
)
if any(item["id"] == operation_id for item in operations):
operation_id = f"{operation_id}_{index}"
parameters: dict[str, Any] = {"position_pct": action["position_pct"]}
if action.get("angle_deg") is not None:
parameters["angle_deg"] = action["angle_deg"]
operation = {
"id": operation_id,
"capability": MOTION_CAPABILITIES[action["axis"]],
"target": TARGETS[action["target"]],
"parameters": parameters,
"timeout_ms": 8000,
"failure_policy": "abort",
}
if action.get("motion_intent") is not None:
operation["motion"] = dict(action["motion_intent"])
operations.append(operation)
assumptions.append(
f"Operation {operation_id} uses an explicitly synthetic position CAN mapping; "
"it is never eligible for hardware transmission."
)
continue
operation_id = f"{action['target']}_{action['function']}_{action['state']}"
if any(item["id"] == operation_id for item in operations):
operation_id = f"{operation_id}_{index}"
parameters: dict[str, Any] = {"state": action["state"]}
if action.get("requested_level") is not None:
parameters["level"] = action["requested_level"]
operations.append(
{
"id": operation_id,
"capability": CAPABILITIES[action["function"]],
"target": TARGETS[action["target"]],
"parameters": parameters,
"timeout_ms": 3000,
"failure_policy": "abort",
}
)
if action.get("requested_level") is not None:
assumptions.append(
f"Operation {operation_id} targets level {action['requested_level']}; "
"the level is simulated because the supplied matrix has no level command signal."
)
return {
"apiVersion": "cognitive-agent/v1alpha1",
"kind": "FunctionConfig",
"metadata": {
"id": f"seat-{request_id}",
"name": "Seat function",
"version": "1.0.0",
"request_id": request_id,
"generated_at": _now(),
"labels": {"provider": provider, "execution_scope": "virtual_only"},
},
"spec": {"domain": "seat", "operations": operations, "assumptions": assumptions},
}
def build_function_preset(
target: str,
preset_name: str,
device_state: dict[str, Any],
request_id: str,
provider: str,
) -> dict[str, Any]:
operations = []
for axis, capability in MOTION_CAPABILITIES.items():
motion_state = device_state["position"][axis]
parameters: dict[str, Any] = {
"position_pct": int(motion_state["position_pct"])
}
if motion_state.get("angle_deg") is not None:
parameters["angle_deg"] = motion_state["angle_deg"]
operations.append(
{
"id": f"{target}_{axis}_preset",
"capability": capability,
"target": TARGETS[target],
"parameters": parameters,
"timeout_ms": 8000,
"failure_policy": "abort",
}
)
return {
"apiVersion": "cognitive-agent/v1alpha1",
"kind": "FunctionPreset",
"metadata": {
"id": f"seat-preset-{request_id}",
"name": preset_name,
"version": "1.0.0",
"request_id": request_id,
"generated_at": _now(),
"description": f"User-saved five-axis posture for {target}.",
"labels": {
"provider": provider,
"target": target,
"storage_scope": "conversation_durable",
},
},
"spec": {
"domain": "seat",
"operations": operations,
"locked_parameters": [
f"operations.{index}.parameters.position_pct"
for index in range(len(operations))
],
"applicability": {
"target": target,
"execution_mode": "dry_run",
},
},
}
+303
View File
@@ -0,0 +1,303 @@
"""Compile seat functions to the CAN protocol implemented by the T2G IAR demos."""
from __future__ import annotations
import hashlib
import json
from pathlib import Path
from typing import Any
import yaml
STATE_VALUES = {"on": 1, "off": 2}
class T2GSeatCompiler:
"""Deterministic compiler for the HIL-verified T2G seat bench protocol."""
version = "1.3.0"
def __init__(self, mapping_path: Path, calibration_path: Path):
self.mapping = yaml.safe_load(mapping_path.read_text(encoding="utf-8"))
self.calibration = yaml.safe_load(calibration_path.read_text(encoding="utf-8"))
self.mapping_version = self.mapping["metadata"]["version"]
self.axes = self.calibration["axes"]
self.protocol = self.calibration["protocol"]
self.supported = {
(item["capability"], item.get("target_zone"))
for item in self.mapping["spec"]["mappings"]
}
def compile(
self,
function_config: dict[str, Any],
actuator_state: dict[str, Any] | None = None,
) -> dict[str, Any]:
operations = function_config["spec"]["operations"]
self._validate_shared_physical_targets(operations)
thermal_operations = [
item
for item in operations
if item["capability"] in {"seat.set_ventilation", "seat.set_heating"}
]
motion_operations = [item for item in operations if item not in thermal_operations]
commands: list[dict[str, Any]] = []
if thermal_operations:
commands.extend(
self._compile_thermal(thermal_operations, actuator_state or {})
)
commands.extend(self._compile_motion(motion_operations, len(commands)))
canonical = json.dumps(
function_config, ensure_ascii=False, sort_keys=True
).encode("utf-8")
metadata = function_config["metadata"]
return {
"apiVersion": "cognitive-agent/v1alpha1",
"kind": "CommandPlan",
"metadata": {
"id": f"plan-t2g-{metadata['request_id']}",
"name": "T2G seat hardware CAN command plan",
"version": "1.0.0",
"request_id": metadata["request_id"],
"labels": {
"execution_scope": "hardware_candidate",
"protocol": "t2g_seat_v1",
},
},
"spec": {
"domain": "seat",
"platform": "t2g_seat_bench",
"source": {
"config_id": metadata["id"],
"config_version": metadata["version"],
"config_sha256": hashlib.sha256(canonical).hexdigest(),
"compiler_version": self.version,
"mapping_version": self.mapping_version,
},
"commands": commands,
},
}
def _compile_thermal(
self,
operations: list[dict[str, Any]],
actuator_state: dict[str, Any],
) -> list[dict[str, Any]]:
state = actuator_state.get("driver") or actuator_state.get("passenger", {})
desired = {
"ventilation": dict(state.get("ventilation", {"state": "off", "level": 0})),
"heating": dict(state.get("heating", {"state": "off", "level": 0})),
}
for operation in operations:
self._require_supported(operation)
feature = operation["capability"].removeprefix("seat.set_")
requested_state = str(operation["parameters"]["state"])
previous_level = int(desired[feature].get("level", 0))
level = (
int(operation["parameters"].get("level", previous_level or 1))
if requested_state == "on"
else 0
)
desired[feature] = {"state": requested_state, "level": level}
operation_ref = ",".join(item["id"] for item in operations)
commands: list[dict[str, Any]] = []
if desired["heating"]["state"] == "on":
commands.append(
{
"id": "t2g_seat_thermal_mode",
"operation_ref": operation_ref,
"bus": "seat_can",
"message": "T2G_Seat_Thermal_Mode_3CE",
"signals": {"Driver_Seat_Thermal_Mode": 1},
"send_offset_ms": 0,
}
)
commands.append(
{
"id": "t2g_seat_thermal_control",
"operation_ref": operation_ref,
"bus": "seat_can",
"message": "T2G_Seat_Thermal_Control_29E",
"signals": {
"Driver_Seat_Heating_Gear": int(desired["heating"]["level"]),
"Driver_Seat_Heating_On": STATE_VALUES[desired["heating"]["state"]],
"Driver_Seat_Ventilation_Gear": int(desired["ventilation"]["level"]),
"Driver_Seat_Ventilation_Mode": 1,
"Driver_Seat_Ventilation_On": STATE_VALUES[
desired["ventilation"]["state"]
],
},
"send_offset_ms": 20 if commands else 0,
}
)
return commands
def _compile_motion(
self, operations: list[dict[str, Any]], command_offset: int
) -> list[dict[str, Any]]:
grouped: list[list[dict[str, Any]]] = []
by_capability: dict[str, list[dict[str, Any]]] = {}
for operation in operations:
self._require_supported(operation)
capability = operation["capability"]
if capability not in by_capability:
by_capability[capability] = []
grouped.append(by_capability[capability])
by_capability[capability].append(operation)
commands = []
for index, physical_operations in enumerate(grouped):
operation = physical_operations[0]
capability = operation["capability"]
try:
axis = self.axes[capability]
except KeyError as exc:
raise ValueError(
f"T2G IAR firmware has no calibrated actuator for {capability}"
) from exc
maximum = int(axis["maximum_value_x100"])
position_pct = int(operation["parameters"]["position_pct"])
target = round(maximum * position_pct / 100)
tolerance = max(1, round(maximum / 100))
motion = operation.get("motion")
if motion is not None and motion.get("mode") == "relative":
delta_pct = int(motion["delta_position_pct"])
maximum_delta = int(axis["max_uncalibrated_relative_pct"])
if delta_pct <= maximum_delta:
commands.append(
self._compile_uncalibrated_relative(
operation,
physical_operations,
axis,
command_offset + index,
)
)
continue
# A large relative request cannot safely be approximated by a
# long open-loop duty command or by several chained commands.
# Compile its already-resolved target as an absolute move. The
# executor will check the board's live Position_Valid bit before
# putting Opcode 1 on the bus.
commands.append(
{
"id": f"t2g_move_{operation['id']}",
"operation_ref": ",".join(
item["id"] for item in physical_operations
),
"bus": "seat_can",
"message": "T2G_Seat_Move_Command_300",
"signals": {
"Direction": 0,
"Motor_Selector": int(axis["motor_selector"]),
"Opcode": int(self.protocol["absolute_position_opcode"]),
"Param_A": target,
"Param_B": int(self.protocol["default_duty_percent"]),
"Protocol_Version": int(self.protocol["version"]),
# Allocated immediately before hardware transmission. Zero keeps
# the compiled plan deterministic and is never put on the bus.
"Sequence": 0,
},
"send_offset_ms": (command_offset + index) * 100,
"completion": {
"message": "T2G_Seat_Move_Motor_Status_302",
"signal": "Value_X100",
"operator": "within",
"value": target,
"tolerance": tolerance,
"timeout_ms": int(operation.get("timeout_ms", 8000)),
},
}
)
return commands
def _compile_uncalibrated_relative(
self,
operation: dict[str, Any],
physical_operations: list[dict[str, Any]],
axis: dict[str, Any],
command_index: int,
) -> dict[str, Any]:
motion = dict(operation["motion"])
delta_pct = int(motion["delta_position_pct"])
maximum_delta = int(axis["max_uncalibrated_relative_pct"])
if delta_pct <= 0:
raise ValueError(
"relative seat movement resolved to zero travel; no motor command was generated"
)
if delta_pct > maximum_delta:
raise ValueError(
"internal compiler error: an oversized relative movement must be "
"compiled as a calibrated absolute target"
)
duration_ms = delta_pct * int(axis["timed_duty_ms_per_pct"])
duration_ms = max(
int(self.protocol["timed_duty_min_duration_ms"]), duration_ms
)
maximum_duration = int(self.protocol["timed_duty_max_duration_ms"])
if duration_ms > maximum_duration:
raise ValueError(
"uncalibrated relative seat movement would exceed the bounded "
f"{maximum_duration} ms timed-motion limit"
)
direction_name = str(motion["direction"])
direction = int(self.protocol[f"{direction_name}_direction"])
timeout_ms = max(
int(operation.get("timeout_ms", 8000)), duration_ms + 3000
)
return {
"id": f"t2g_move_{operation['id']}",
"operation_ref": ",".join(
item["id"] for item in physical_operations
),
"bus": "seat_can",
"message": "T2G_Seat_Move_Command_300",
"signals": {
"Direction": direction,
"Motor_Selector": int(axis["motor_selector"]),
"Opcode": int(self.protocol["timed_duty_opcode"]),
"Param_A": duration_ms,
"Param_B": int(self.protocol["default_duty_percent"]),
"Protocol_Version": int(self.protocol["version"]),
"Sequence": 0,
},
"send_offset_ms": command_index * 100,
"completion": {
"message": "T2G_Seat_Move_Motor_Status_302",
"signal": "Task_State",
"operator": "eq",
"value": 8,
"timeout_ms": timeout_ms,
},
}
@staticmethod
def _validate_shared_physical_targets(
operations: list[dict[str, Any]],
) -> None:
requested: dict[str, dict[str, Any]] = {}
for operation in operations:
capability = operation["capability"]
request = {
"parameters": dict(operation.get("parameters", {})),
"motion": dict(operation["motion"]) if operation.get("motion") else None,
}
previous = requested.get(capability)
if previous is not None and previous != request:
raise ValueError(
"driver and passenger aliases request conflicting values for "
f"the same physical capability {capability}"
)
requested[capability] = request
def _require_supported(self, operation: dict[str, Any]) -> None:
key = (operation["capability"], operation["target"]["zone"])
if key not in self.supported:
raise ValueError(
"T2G hardware mapping does not support "
f"capability={key[0]}, zone={key[1]}"
)
+713
View File
@@ -0,0 +1,713 @@
"""Hardware-in-the-loop executor for the verified T2G seat CAN protocol."""
from __future__ import annotations
import threading
import time
from datetime import UTC, datetime
from typing import Any, Callable
from cognitive_agent.infrastructure.can.gateway import CanBus, CanFrame
from cognitive_agent.infrastructure.firmware import T2GFirmwareGuard
from .executor import MOTION_CAPABILITY_TO_AXIS
from .matrix import SeatCanMatrix
MOTOR_RESULT_NAMES = {
0: "NONE",
1: "RUNNING",
2: "OK",
3: "STOPPED",
4: "STALL",
5: "ENDPOINT",
6: "TIMEOUT",
7: "DRIVER_FAULT",
8: "INVALID",
9: "CALIBRATION_FAILED",
}
MOTOR_EVENT_NAMES = {
0: "NONE",
1: "TARGET_REACHED",
2: "NEAR_ENDPOINT",
3: "FAR_ENDPOINT",
4: "STALL",
5: "TIMEOUT",
6: "ABORTED",
7: "DRIVER_FAULT",
8: "CALIBRATION_FAILED",
}
class HardwareExecutionError(RuntimeError):
"""Raised for rejected, faulted or timed-out physical operations."""
def __init__(
self, message: str, *, telemetry: dict[str, Any] | None = None
) -> None:
super().__init__(message)
self.telemetry = dict(telemetry or {})
def _now() -> str:
return datetime.now(UTC).isoformat().replace("+00:00", "Z")
class HardwareSeatExecutor:
"""Serialize access to one adapter and require real firmware feedback."""
_adapter_lock = threading.Lock()
_sequence = 0
def __init__(
self,
matrix: SeatCanMatrix,
bus_factory: Callable[[], CanBus],
*,
thermal_timeout_ms: int = 2_000,
ack_timeout_ms: int = 1_500,
axis_maximum_x100: dict[str, int] | None = None,
firmware_guard: T2GFirmwareGuard | None = None,
):
self.matrix = matrix
self.bus_factory = bus_factory
self.thermal_timeout_ms = thermal_timeout_ms
self.ack_timeout_ms = ack_timeout_ms
self.axis_maximum_x100 = dict(axis_maximum_x100 or {})
self.firmware_guard = firmware_guard
@classmethod
def _next_sequence(cls) -> int:
cls._sequence = (cls._sequence + 1) & 0xFFFF
if cls._sequence == 0:
cls._sequence = 1
return cls._sequence
def execute(
self,
command_plan: dict[str, Any],
function_config: dict[str, Any],
actuator_state: dict[str, Any] | None = None,
) -> dict[str, Any]:
del actuator_state # physical feedback, not virtual state, is authoritative here
started_at = _now()
operations = {
item["id"]: item for item in function_config["spec"]["operations"]
}
results: list[dict[str, Any]] = []
commands = command_plan["spec"]["commands"]
has_motion = any(item["message"] == "T2G_Seat_Move_Command_300" for item in commands)
bus: CanBus | None = None
motion_armed = False
with self._adapter_lock:
try:
if self.firmware_guard is not None:
bus, firmware_telemetry = self.firmware_guard.prepare_bus()
results.append(
{
"ref": "seat-controller-firmware",
"domain": "seat",
"status": "succeeded",
"message": (
"repository Seat_Controller_demo was programmed and verified"
if firmware_telemetry.get("programmed_this_request")
else "repository Seat_Controller_demo identity was verified"
),
"telemetry": {
**firmware_telemetry,
"simulated": False,
},
}
)
else:
bus = self.bus_factory()
if has_motion:
self._stop_all(bus, require_ack=True)
motion_armed = True
previous_offset = 0
for command in commands:
offset = int(command.get("send_offset_ms", 0))
if offset > previous_offset:
time.sleep((offset - previous_offset) / 1000)
previous_offset = offset
operation_ids = command["operation_ref"].split(",")
referenced = [operations[item] for item in operation_ids]
if command["message"] == "T2G_Seat_Thermal_Mode_3CE":
frame = self._send_encoded(bus, command["message"], command["signals"])
results.append(
{
"ref": command["id"],
"domain": "seat",
"status": "succeeded",
"message": "T2G heater manual control mode sent to the physical CAN bus",
"telemetry": {
"control_step": "thermal_mode",
"command_frame": frame.public(),
"simulated": False,
},
}
)
elif command["message"] == "T2G_Seat_Thermal_Control_29E":
results.extend(self._execute_thermal(bus, command, referenced))
elif command["message"] == "T2G_Seat_Move_Command_300":
results.extend(self._execute_motion(bus, command, referenced))
else:
raise HardwareExecutionError(
f"hardware executor refuses unknown message {command['message']}"
)
except Exception as exc: # noqa: BLE001 - normalize transport and protocol failures
if bus is not None and motion_armed:
self._best_effort_stop(bus)
operation_ref = self._first_unreported_operation(operations, results)
results.append(
{
"ref": operation_ref,
"domain": "seat",
"status": "failed",
"message": str(exc),
"telemetry": {
**getattr(exc, "telemetry", {}),
"error_type": type(exc).__name__,
"stop_all_attempted": bool(bus is not None and motion_armed),
"simulated": False,
},
}
)
finally:
if bus is not None:
bus.close()
succeeded = all(item["status"] == "succeeded" for item in results)
request_id = function_config["metadata"]["request_id"]
return {
"apiVersion": "cognitive-agent/v1alpha1",
"kind": "ExecutionReport",
"metadata": {
"id": f"report-{request_id}",
"name": "T2G seat hardware execution report",
"version": "1.0.0",
"request_id": request_id,
"labels": {"simulation": "false", "feedback": "physical_can"},
},
"spec": {
"mode": "hil",
"status": "succeeded" if succeeded else "failed",
"started_at": started_at,
"finished_at": _now(),
"results": results,
},
}
@staticmethod
def _first_unreported_operation(
operations: dict[str, dict[str, Any]], results: list[dict[str, Any]]
) -> str:
reported = {item["ref"] for item in results}
return next((item for item in operations if item not in reported), next(iter(operations)))
def _send_encoded(
self, bus: CanBus, message: str, signals: dict[str, int]
) -> CanFrame:
encoded = self.matrix.encode(message, signals)
return bus.send(int(encoded["frame_id"], 16), bytes.fromhex(encoded["data_hex"]))
def _receive_until(
self,
bus: CanBus,
arbitration_id: int,
timeout_ms: int,
predicate: Callable[[dict[str, int], CanFrame], bool],
) -> tuple[dict[str, int], CanFrame]:
deadline = time.monotonic() + timeout_ms / 1000
while True:
remaining_ms = round((deadline - time.monotonic()) * 1000)
if remaining_ms <= 0:
raise HardwareExecutionError(
f"timed out after {timeout_ms} ms waiting for CAN 0x{arbitration_id:03X}"
)
frame = bus.receive(remaining_ms)
if frame is None:
raise HardwareExecutionError(
f"timed out after {timeout_ms} ms waiting for CAN 0x{arbitration_id:03X}"
)
if frame.arbitration_id != arbitration_id or len(frame.data) < 8:
continue
values = self.matrix.decode_by_frame_id(arbitration_id, frame.data)
if predicate(values, frame):
return values, frame
def _wait_ack(
self, bus: CanBus, sequence: int, opcode: int
) -> tuple[dict[str, int], CanFrame]:
def matches(values: dict[str, int], _: CanFrame) -> bool:
if values["Last_Sequence"] != sequence or values["Last_Opcode"] != opcode:
return False
if values["Ack"] != 1:
raise HardwareExecutionError(
"T2G movement command rejected: "
f"ack={values['Ack']}, reason={values['Reject_Reason']}, "
f"safety_fault={values['Safety_Fault']}"
)
if values["Safety_Inhibit"] or values["Safety_Fault"]:
raise HardwareExecutionError(
f"T2G movement safety inhibit/fault: {values['Safety_Fault']}"
)
return True
return self._receive_until(bus, 0x301, self.ack_timeout_ms, matches)
def _stop_all(self, bus: CanBus, *, require_ack: bool) -> tuple[CanFrame, CanFrame | None]:
sequence = self._next_sequence()
signals = {
"Opcode": 9,
"Motor_Selector": 3,
"Direction": 0,
"Protocol_Version": 1,
"Sequence": sequence,
"Param_A": 0,
"Param_B": 0,
}
sent = self._send_encoded(bus, "T2G_Seat_Move_Command_300", signals)
if not require_ack:
return sent, None
_, ack = self._wait_ack(bus, sequence, 9)
return sent, ack
def _best_effort_stop(self, bus: CanBus) -> None:
try:
self._stop_all(bus, require_ack=False)
except Exception: # noqa: BLE001 - already handling the primary failure
pass
def _execute_thermal(
self,
bus: CanBus,
command: dict[str, Any],
operations: list[dict[str, Any]],
) -> list[dict[str, Any]]:
command_frame = self._send_encoded(bus, command["message"], command["signals"])
expected: dict[str, int] = {}
for operation in operations:
feature = operation["capability"].removeprefix("seat.set_")
signal = (
"Driver_Seat_Heating_Status"
if feature == "heating"
else "Driver_Seat_Ventilation_Status"
)
state = operation["parameters"]["state"]
level = int(operation["parameters"].get("level", 1))
expected[signal] = 1 if state == "off" else min(level, 3) + 1
def matches(values: dict[str, int], _: CanFrame) -> bool:
return all(values[name] == value for name, value in expected.items())
values, feedback = self._receive_until(
bus, 0x4FA, self.thermal_timeout_ms, matches
)
output = []
for operation in operations:
feature = operation["capability"].removeprefix("seat.set_")
signal = (
"Driver_Seat_Heating_Status"
if feature == "heating"
else "Driver_Seat_Ventilation_Status"
)
raw = values[signal]
output.append(
{
"ref": operation["id"],
"domain": "seat",
"status": "succeeded",
"message": (
f"physical 0x4FA confirmed shared front-seat {feature} "
f"state={operation['parameters']['state']}, level={max(0, raw - 1)}"
),
"telemetry": {
"requested_target": operation["target"]["instance"],
"physical_target": "t2g_primary_seat",
"requested_state": operation["parameters"]["state"],
"requested_level": operation["parameters"].get("level"),
"feedback_signal": signal,
"feedback_value": raw,
"command_frame": command_frame.public(),
"feedback_frame": feedback.public(),
"hardware_feedback": True,
"simulated": False,
},
}
)
return output
def _execute_motion(
self,
bus: CanBus,
command: dict[str, Any],
operations: list[dict[str, Any]],
) -> list[dict[str, Any]]:
if not operations:
raise HardwareExecutionError(
"a T2G motion frame must reference at least one operation"
)
operation = operations[0]
if any(
item["capability"] != operation["capability"]
or item["parameters"] != operation["parameters"]
or item.get("motion") != operation.get("motion")
for item in operations[1:]
):
raise HardwareExecutionError(
"one shared T2G motion frame cannot represent conflicting operations"
)
signals = dict(command["signals"])
motor = int(signals["Motor_Selector"])
opcode = int(signals["Opcode"])
if opcode == 3:
return self._execute_timed_motion(
bus, command, operations, signals, motor
)
preflight_values, preflight_frame = self._motor_status_snapshot(bus, motor)
if not preflight_values["Position_Valid"]:
raise HardwareExecutionError(
f"T2G motor {motor} absolute-position command blocked before send: "
"controller Position_Valid=0. Run the explicit endpoint-calibration "
"maintenance procedure with the bench clear and emergency stop ready, "
"or use a small relative movement request.",
telemetry={
"motor_selector": motor,
"position_valid": False,
"calibration_required": True,
"preflight_frame": preflight_frame.public(),
},
)
sequence = self._next_sequence()
signals["Sequence"] = sequence
command_frame = self._send_encoded(bus, command["message"], signals)
ack_values, ack_frame = self._wait_ack(bus, sequence, opcode)
target = int(command["completion"]["value"])
tolerance = int(command["completion"].get("tolerance", 0))
def complete(values: dict[str, int], _: CanFrame) -> bool:
if values["Motor_Sub_ID"] != motor or values["Motor_Sequence"] != sequence:
return False
task_state = values["Task_State"]
result = values["Result"]
if task_state == 9 or result not in {1, 2}:
raise HardwareExecutionError(
f"T2G motor {motor} failed: task_state={task_state}, result={result}"
)
if task_state != 8 or result != 2:
return False
if not values["Position_Valid"]:
raise HardwareExecutionError(
f"T2G motor {motor} completed without a valid calibrated position"
)
if abs(values["Value_X100"] - target) > tolerance:
raise HardwareExecutionError(
f"T2G motor {motor} stopped outside tolerance: "
f"actual={values['Value_X100']}, target={target}, tolerance={tolerance}"
)
return True
values, feedback = self._receive_until(
bus,
0x302,
int(command["completion"]["timeout_ms"]),
complete,
)
axis = MOTION_CAPABILITY_TO_AXIS[operation["capability"]]
maximum = self.axis_maximum_x100.get(operation["capability"])
if maximum is None:
requested_pct = int(operation["parameters"]["position_pct"])
maximum = max(1, target * 100 // max(1, requested_pct))
actual_pct = round(values["Value_X100"] * 100 / maximum)
common_telemetry = {
"motion_axis": axis,
"motor_selector": motor,
"physical_target": "t2g_primary_seat",
"sequence": sequence,
"requested_position_pct": int(operation["parameters"]["position_pct"]),
"effective_position_pct": actual_pct,
"requested_value_x100": target,
"effective_value_x100": values["Value_X100"],
"position_tolerance_x100": tolerance,
"task_state": values["Task_State"],
"motor_result": values["Result"],
"position_valid": bool(values["Position_Valid"]),
"system_ack": ack_values["Ack"],
"preflight_frame": preflight_frame.public(),
"command_frame": command_frame.public(),
"ack_frame": ack_frame.public(),
"feedback_frame": feedback.public(),
"hardware_feedback": True,
"simulated": False,
}
return [
{
"ref": item["id"],
"domain": "seat",
"status": "succeeded",
"message": (
f"physical motor {motor} feedback confirmed shared front-seat "
f"{axis} at {actual_pct}%"
),
"telemetry": {
**common_telemetry,
"requested_target": item["target"]["instance"],
},
}
for item in operations
]
def _motor_status_snapshot(
self, bus: CanBus, motor: int
) -> tuple[dict[str, int], CanFrame]:
return self._receive_until(
bus,
0x302,
max(self.ack_timeout_ms, 1_000),
lambda values, _: values["Motor_Sub_ID"] == motor,
)
def _motor_diagnostic_snapshot(
self, bus: CanBus, motor: int
) -> tuple[dict[str, int], CanFrame]:
return self._receive_until(
bus,
0x303,
max(self.ack_timeout_ms, 1_000),
lambda values, _: values["Motor_Sub_ID"] == motor,
)
def _execute_timed_motion(
self,
bus: CanBus,
command: dict[str, Any],
operations: list[dict[str, Any]],
signals: dict[str, int],
motor: int,
) -> list[dict[str, Any]]:
baseline, baseline_frame = self._motor_diagnostic_snapshot(bus, motor)
sequence = self._next_sequence()
signals["Sequence"] = sequence
command_frame = self._send_encoded(bus, command["message"], signals)
ack_values, ack_frame = self._wait_ack(bus, sequence, 3)
def terminal(values: dict[str, int], _: CanFrame) -> bool:
if values["Motor_Sub_ID"] != motor or values["Motor_Sequence"] != sequence:
return False
task_state = values["Task_State"]
result = values["Result"]
return task_state in {8, 9} or result not in {1, 2}
values, feedback = self._receive_until(
bus,
0x302,
int(command["completion"]["timeout_ms"]),
terminal,
)
diagnostic: dict[str, int] | None = None
diagnostic_frame: CanFrame | None = None
diagnostic_error: str | None = None
try:
diagnostic, diagnostic_frame = self._motor_diagnostic_snapshot(bus, motor)
except HardwareExecutionError as exc:
diagnostic_error = str(exc)
hall_before = int(baseline["Hall_Count"])
hall_after = (
int(diagnostic["Hall_Count"]) if diagnostic is not None else hall_before
)
direction = int(signals["Direction"])
task_state = int(values["Task_State"])
result = int(values["Result"])
result_name = MOTOR_RESULT_NAMES.get(result, f"UNKNOWN_{result}")
last_event = (
int(diagnostic["Last_Motor_Event"])
if diagnostic is not None
else None
)
last_event_name = (
MOTOR_EVENT_NAMES.get(last_event, f"UNKNOWN_{last_event}")
if last_event is not None
else "UNAVAILABLE"
)
hall_delta = abs(hall_after - hall_before)
max_hall = max(
1,
int(diagnostic["Max_Hall_Count"])
if diagnostic is not None
else int(baseline["Max_Hall_Count"]),
)
if task_state != 8 or result != 2:
if result == 4 and hall_delta <= 2:
failure_category = "stall_no_hall_activity"
diagnostic_hint = (
"No sustained Hall activity was observed. The commanded direction may "
"already be at a mechanical endpoint or obstructed, or the Motor Hall "
"power/signal path may be disconnected. With the bench clear, use a "
"small command in the opposite direction to distinguish endpoint from "
"a Hall wiring fault; do not run automatic full-range calibration."
)
elif result == 4:
failure_category = "stall_after_hall_activity"
diagnostic_hint = (
"Hall activity was observed before the stall; inspect the mechanism for "
"an endpoint or obstruction in the commanded direction."
)
elif result == 5 or last_event in {2, 3}:
failure_category = "endpoint"
diagnostic_hint = (
"The controller reported an endpoint. Verify the requested direction "
"and establish endpoint calibration before absolute positioning."
)
else:
failure_category = result_name.lower()
diagnostic_hint = (
"Inspect the controller result, last motor event, driver supply and Hall "
"feedback before retrying the movement."
)
raise HardwareExecutionError(
f"T2G timed motor {motor} {result_name}: task_state={task_state}, "
f"Hall={hall_before}->{hall_after}, last_event={last_event_name}. "
f"{diagnostic_hint}",
telemetry={
"motor_selector": motor,
"motion_axis": MOTION_CAPABILITY_TO_AXIS[
operations[0]["capability"]
],
"motion_mode": "relative_timed_duty",
"position_valid": bool(values["Position_Valid"]),
"task_state": task_state,
"motor_result": result,
"motor_result_name": result_name,
"last_motor_event": last_event,
"last_motor_event_name": last_event_name,
"failure_category": failure_category,
"diagnostic_hint": diagnostic_hint,
"hall_before": hall_before,
"hall_after": hall_after,
"hall_delta": hall_delta,
"max_hall_count": max_hall,
"direction": direction,
"duration_ms": int(signals["Param_A"]),
"duty_percent": int(signals["Param_B"]),
"command_frame": command_frame.public(),
"ack_frame": ack_frame.public(),
"feedback_frame": feedback.public(),
"diagnostic_before_frame": baseline_frame.public(),
"diagnostic_frame": (
diagnostic_frame.public()
if diagnostic_frame is not None
else None
),
"diagnostic_read_error": diagnostic_error,
},
)
if diagnostic is None or diagnostic_frame is None:
raise HardwareExecutionError(
f"T2G timed motor {motor} completed but diagnostic Hall feedback "
f"was unavailable: {diagnostic_error}",
telemetry={
"motor_selector": motor,
"position_valid": bool(values["Position_Valid"]),
"task_state": task_state,
"motor_result": result,
"motor_result_name": result_name,
"hall_before": hall_before,
"command_frame": command_frame.public(),
"ack_frame": ack_frame.public(),
"feedback_frame": feedback.public(),
"diagnostic_before_frame": baseline_frame.public(),
"diagnostic_read_error": diagnostic_error,
},
)
direction_verified: bool | None
if direction == 1:
direction_verified = hall_after > hall_before
elif hall_before > 0:
direction_verified = hall_after < hall_before
else:
# At an uncalibrated zero estimate, NEAR movement cannot decrement
# the public Hall position. COMPLETE/OK still proves raw Hall edges
# because the firmware refuses timed success without one.
direction_verified = None
if direction_verified is False:
raise HardwareExecutionError(
f"T2G timed motor {motor} completed but Hall direction did not match "
f"the command: before={hall_before}, after={hall_after}, direction={direction}",
telemetry={
"motor_selector": motor,
"position_valid": bool(values["Position_Valid"]),
"hall_before": hall_before,
"hall_after": hall_after,
"hall_direction_verified": False,
"feedback_frame": feedback.public(),
"diagnostic_frame": diagnostic_frame.public(),
},
)
axis = MOTION_CAPABILITY_TO_AXIS[operations[0]["capability"]]
common_telemetry = {
"motion_axis": axis,
"motion_mode": "relative_timed_duty",
"motor_selector": motor,
"physical_target": "t2g_primary_seat",
"sequence": sequence,
"requested_position_pct": int(
operations[0]["parameters"]["position_pct"]
),
"requested_delta_position_pct": int(
operations[0].get("motion", {}).get("delta_position_pct", 0)
),
"requested_delta_angle_deg": operations[0]
.get("motion", {})
.get("delta_angle_deg"),
"duration_ms": int(signals["Param_A"]),
"duty_percent": int(signals["Param_B"]),
"direction": direction,
"task_state": task_state,
"motor_result": result,
"motor_result_name": result_name,
"last_motor_event": last_event,
"last_motor_event_name": last_event_name,
"position_valid": bool(values["Position_Valid"]),
"absolute_position_available": bool(values["Position_Valid"]),
"hall_before": hall_before,
"hall_after": hall_after,
"hall_delta": hall_delta,
"max_hall_count": max_hall,
"observed_delta_position_pct": round(hall_delta * 100 / max_hall),
"hall_direction_verified": direction_verified,
"system_ack": ack_values["Ack"],
"command_frame": command_frame.public(),
"ack_frame": ack_frame.public(),
"feedback_frame": feedback.public(),
"diagnostic_before_frame": baseline_frame.public(),
"diagnostic_frame": diagnostic_frame.public(),
"hardware_feedback": True,
"simulated": False,
}
return [
{
"ref": item["id"],
"domain": "seat",
"status": "succeeded",
"message": (
f"physical motor {motor} COMPLETE/OK confirmed relative {axis} "
f"movement with Hall {hall_before}->{hall_after}; absolute position "
"remains unavailable until endpoint calibration"
),
"telemetry": {
**common_telemetry,
"requested_target": item["target"]["instance"],
},
}
for item in operations
]
+433
View File
@@ -0,0 +1,433 @@
"""Deterministic fallback parser for the supported seat thermal vocabulary."""
from __future__ import annotations
from copy import deepcopy
import re
from typing import Any
TARGET_PATTERN = re.compile(r"主副驾|前排|主驾|驾驶位|司机位|副驾|乘客位")
FEATURE_PATTERN = re.compile(r"通风|加热")
STATE_PATTERN = re.compile(r"关闭|关掉|停止|取消|打开|开启|启动|开")
LEVEL_PATTERN = re.compile(r"([1-5一二三四五])\s*档")
VENTILATION_FEEDBACK_PATTERN = re.compile(r"(?<!不)(?<!加)热|(?<!不)闷|出汗")
HEATING_FEEDBACK_PATTERN = re.compile(r"(?<!不)冷|(?<!不)凉")
MILD_PATTERN = re.compile(r"有点|一点|稍微|微微")
STRONG_PATTERN = re.compile(r"很|太|非常|特别|难受")
AIRFLOW_CAUSE_PATTERN = re.compile(r"吹|风太大|风吹")
FIRST_PERSON_PATTERN = re.compile(r"我|自己")
VEHICLE_SCOPE_PATTERN = re.compile(r"车上|车内|车里|全车|整车|整个车")
TARGETS = {
"主驾": ["driver"],
"驾驶位": ["driver"],
"司机位": ["driver"],
"副驾": ["passenger"],
"乘客位": ["passenger"],
"主副驾": ["driver", "passenger"],
"前排": ["driver", "passenger"],
}
FEATURES = {"通风": "ventilation", "加热": "heating"}
STATES = {
"打开": "on",
"开启": "on",
"启动": "on",
"开": "on",
"关闭": "off",
"关掉": "off",
"停止": "off",
"取消": "off",
}
LEVELS = {"一": 1, "二": 2, "三": 3, "四": 4, "五": 5}
def _nearest_preceding_target(clause: str, position: int) -> str | None:
candidates = [match for match in TARGET_PATTERN.finditer(clause) if match.start() <= position]
if candidates:
return candidates[-1].group(0)
matches = list(TARGET_PATTERN.finditer(clause))
return matches[0].group(0) if len(matches) == 1 else None
def _state_for_feature(clause: str, position: int) -> str | None:
states = list(STATE_PATTERN.finditer(clause))
if not states:
return None
following = [match for match in states if match.start() >= position]
if following and following[0].start() - position <= 8:
return STATES[following[0].group(0)]
if len(states) == 1:
return STATES[states[0].group(0)]
preceding = [match for match in states if match.start() < position]
return STATES[preceding[-1].group(0)] if preceding else None
def _level_for_clause(clause: str) -> int | None:
match = LEVEL_PATTERN.search(clause)
if not match:
return None
token = match.group(1)
return int(token) if token.isdigit() else LEVELS[token]
def _context_device(
context: dict[str, Any] | None, target: str, function: str
) -> dict[str, Any]:
if not context:
return {"state": "off", "level": 0}
return (
context.get("devices", {})
.get(target, {})
.get(function, {"state": "off", "level": 0})
)
def _feedback_targets(
text: str, context: dict[str, Any] | None, parsed: dict[str, Any]
) -> tuple[list[str], str]:
matches = list(TARGET_PATTERN.finditer(text))
if matches:
targets: list[str] = []
for match in matches:
for target in TARGETS[match.group(0)]:
if target not in targets:
targets.append(target)
return targets, "explicit"
parsed_targets = list(
dict.fromkeys(
action.get("target")
for action in parsed.get("actions", [])
if action.get("target") in {"driver", "passenger"}
)
)
if parsed_targets:
return parsed_targets, "model"
last_target = context.get("last_target") if context else None
if last_target in {"driver", "passenger"}:
return [last_target], "history"
if FIRST_PERSON_PATTERN.search(text):
return ["driver"], "speaker_default"
return [], "missing"
def _inferred_level(text: str) -> tuple[int, str]:
explicit_level = _level_for_clause(text)
if explicit_level is not None:
return explicit_level, "explicit"
if STRONG_PATTERN.search(text):
return 3, "strong"
if MILD_PATTERN.search(text):
return 1, "mild"
return 2, "normal"
def _decrease_action(
target: str, function: str, current: dict[str, Any]
) -> tuple[dict[str, Any], int, int]:
previous_level = int(current.get("level") or 0)
if current.get("state") == "on" and previous_level > 1:
next_level = previous_level - 1
return (
{
"target": target,
"function": function,
"state": "on",
"requested_level": next_level,
},
previous_level,
next_level,
)
return (
{
"target": target,
"function": function,
"state": "off",
"requested_level": None,
},
previous_level,
0,
)
def resolve_contextual_intent(
parsed: dict[str, Any], utterance: str, context: dict[str, Any] | None = None
) -> dict[str, Any]:
"""Resolve implicit comfort feedback against the current virtual seat state."""
result = deepcopy(parsed)
text = re.sub(r"\s+", "", utterance.strip())
ventilation_cues = list(dict.fromkeys(VENTILATION_FEEDBACK_PATTERN.findall(text)))
cold_cues = list(dict.fromkeys(HEATING_FEEDBACK_PATTERN.findall(text)))
if not ventilation_cues and not cold_cues:
return result
if VEHICLE_SCOPE_PATTERN.search(text) and not TARGET_PATTERN.search(text):
result["actions"] = []
result["ambiguities"] = [
{
"field": "domain",
"question": "该请求作用于整车舱空气,需要由空调/新风域执行;当前座椅单域不能代替执行。",
}
]
result["decision_summary"] = [
"“车上/车内”表示整车舱范围,不是主驾或副驾单座位。",
"“太闷”属于舱内空气质量/新风需求,应路由到 climate 域。",
"当前未接入空调执行器,未将整车请求错误下发到任一座椅。",
]
result["detected_domains"] = ["climate"]
result["scope"] = "vehicle"
result["confidence"] = max(float(result.get("confidence", 0)), 0.96)
return result
targets, target_source = _feedback_targets(text, context, result)
if not targets:
if not any(item.get("field") == "target" for item in result["ambiguities"]):
result["ambiguities"].append(
{"field": "target", "question": "该舒适反馈需要作用于主驾还是副驾?"}
)
return result
labels = {
"driver": "主驾",
"passenger": "副驾",
"ventilation": "通风",
"heating": "加热",
}
summaries: list[str] = []
if target_source == "history":
summaries.append(f"本轮省略了座位,沿用上一轮的{labels[targets[0]]}目标。")
elif target_source == "speaker_default":
summaries.append("当前为座椅单域驾驶员交互,将第一人称“我”解析为主驾。")
planned_actions: list[dict[str, Any]] = []
desired_level, intensity = _inferred_level(text)
airflow_is_cause = bool(AIRFLOW_CAUSE_PATTERN.search(text))
is_cold = bool(cold_cues)
is_hot = any(cue in {"热", "出汗"} for cue in ventilation_cues)
for target in targets:
ventilation = _context_device(context, target, "ventilation")
heating = _context_device(context, target, "heating")
cue_text = "、".join(cold_cues if is_cold else ventilation_cues)
summaries.append(f"检测到{labels[target]}“{cue_text}”舒适反馈,开始核对当前执行器状态。")
if cold_cues and ventilation_cues and not airflow_is_cause:
balanced_level = min(desired_level, 3)
planned_actions.extend(
[
{
"target": target,
"function": "heating",
"state": "on",
"requested_level": balanced_level,
},
{
"target": target,
"function": "ventilation",
"state": "on",
"requested_level": balanced_level,
},
]
)
summaries[-1] = (
f"检测到{labels[target]}“{'、'.join(cold_cues + ventilation_cues)}”两个并存的舒适维度,"
"不将其判定为语义冲突。"
)
summaries.append(
f"冷感由{labels[target]}加热{balanced_level}档补偿,闷感由"
f"{labels[target]}通风{balanced_level}档缓解;生成可审计的组合目标。"
)
continue
if is_cold and (airflow_is_cause or ventilation.get("state") == "on"):
action, previous_level, next_level = _decrease_action(
target, "ventilation", ventilation
)
planned_actions.append(action)
if airflow_is_cause:
summaries.append(
f"“{labels[target]}吹得冷”明确将{labels[target]}通风指定为致冷来源,不映射为加热请求。"
)
if previous_level > 1:
summaries.append(
f"当前{labels[target]}通风为{previous_level}档,先降低到{next_level}档,不同时调整加热。"
)
elif previous_level == 1:
summaries.append(
f"当前{labels[target]}通风为1档,冷感更可能由已开启通风造成;"
"先关闭通风,不立即开启加热。"
)
else:
summaries.append(
f"当前状态显示{labels[target]}通风已关闭,保持关闭;不会因“吹”反向调整加热。"
)
continue
if is_cold:
current_level = int(heating.get("level") or 0)
if heating.get("state") == "on" and current_level > 0:
next_level = min(max(current_level + 1, desired_level), 3)
planned_actions.append(
{
"target": target,
"function": "heating",
"state": "on",
"requested_level": next_level,
}
)
summaries.append(
f"{labels[target]}通风已关闭,加热已开启{current_level}档但仍感到冷,"
f"将加热提升到{next_level}档。"
)
else:
planned_actions.append(
{
"target": target,
"function": "heating",
"state": "on",
"requested_level": desired_level,
}
)
summaries.append(
f"{labels[target]}通风已关闭且加热未开启,冷感仍存在;"
f"按{'轻微' if intensity == 'mild' else '当前'}强度开启加热{desired_level}档。"
)
continue
if is_hot and heating.get("state") == "on":
action, previous_level, next_level = _decrease_action(target, "heating", heating)
planned_actions.append(action)
summaries.append(
f"当前{labels[target]}加热为{previous_level}档,热感优先视为已开启加热的结果;"
f"先{'关闭加热' if next_level == 0 else f'降低到{next_level}档'},不立即开启通风。"
)
continue
current_level = int(ventilation.get("level") or 0)
if ventilation.get("state") == "on" and current_level > 0:
next_level = min(max(current_level + 1, desired_level), 3)
summaries.append(
f"{labels[target]}通风已开启{current_level}档但舒适问题仍存在,提升到{next_level}档。"
)
else:
next_level = desired_level
summaries.append(
f"{labels[target]}没有已开启的加热致因,按当前强度开启通风{next_level}档。"
)
planned_actions.append(
{
"target": target,
"function": "ventilation",
"state": "on",
"requested_level": next_level,
}
)
result["actions"] = planned_actions
result["ambiguities"] = [
item
for item in result["ambiguities"]
if item.get("field") not in {"capability", "state", "target"}
]
result["decision_summary"] = summaries
result["confidence"] = max(float(result.get("confidence", 0)), 0.9)
return result
def parse_local_intent(
utterance: str, context: dict[str, Any] | None = None
) -> dict[str, Any]:
"""Parse common Chinese seat commands without making hardware assumptions."""
text = re.sub(r"\s+", "", utterance.strip())
clauses = [part for part in re.split(r"[,,。;;!!??]", text) if part]
actions: list[dict[str, Any]] = []
ambiguities: list[dict[str, str]] = []
for clause in clauses:
level = _level_for_clause(clause)
for feature_match in FEATURE_PATTERN.finditer(clause):
feature_zh = feature_match.group(0)
target_zh = _nearest_preceding_target(clause, feature_match.start())
state = _state_for_feature(clause, feature_match.end())
if target_zh is None:
ambiguities.append(
{"field": "target", "question": f"“{feature_zh}”需要作用于主驾还是副驾?"}
)
continue
if state is None:
ambiguities.append(
{"field": "state", "question": f"请确认{target_zh}{feature_zh}是打开还是关闭。"}
)
continue
for target in TARGETS[target_zh]:
actions.append(
{
"target": target,
"function": FEATURES[feature_zh],
"state": state,
"requested_level": level,
}
)
merged: dict[tuple[str, str], dict[str, Any]] = {}
for action in actions:
key = (action["target"], action["function"])
previous = merged.get(key)
if previous and previous["state"] != action["state"]:
ambiguities.append(
{
"field": f"{key[0]}.{key[1]}.state",
"question": "同一个座椅功能同时出现打开和关闭指令,请确认最终状态。",
}
)
merged[key] = action
actions = list(merged.values())
parsed = resolve_contextual_intent(
{
"actions": actions,
"ambiguities": ambiguities,
"decision_summary": [],
"confidence": 0.96 if actions and not ambiguities else 0.45,
},
utterance,
context,
)
actions = parsed["actions"]
ambiguities = parsed["ambiguities"]
if not actions and not ambiguities:
ambiguities.append(
{
"field": "capability",
"question": "当前仅支持主/副驾座椅通风或加热的打开与关闭。",
}
)
summary = ["识别为座椅热舒适单域请求。", *parsed["decision_summary"]]
labels = {
"driver": "主驾",
"passenger": "副驾",
"ventilation": "通风",
"heating": "加热",
"on": "打开",
"off": "关闭",
}
summary.extend(
f"目标:{labels[action['target']]}座椅{labels[action['function']]}{labels[action['state']]}。"
for action in actions
)
if any(action["requested_level"] is not None for action in actions):
summary.append("目标档位已写入功能配置;现有 CAN 矩阵仅支持档位虚拟回读。")
response = {
"actions": actions,
"ambiguities": ambiguities,
"decision_summary": summary,
"confidence": max(parsed["confidence"], 0.96) if actions and not ambiguities else 0.45,
}
for field in ("detected_domains", "scope"):
if field in parsed:
response[field] = parsed[field]
return response
+115
View File
@@ -0,0 +1,115 @@
"""Load and encode the normalized partial seat CAN matrix."""
from __future__ import annotations
from pathlib import Path
from collections.abc import Iterable
from typing import Any
import yaml
class MatrixError(ValueError):
"""Raised when a command cannot be represented by the normalized matrix."""
class SeatCanMatrix:
def __init__(self, matrix_path: Path | Iterable[Path]):
paths = [matrix_path] if isinstance(matrix_path, Path) else list(matrix_path)
if not paths:
raise MatrixError("At least one matrix document is required")
self.path = paths[0]
self.paths = paths
documents = [yaml.safe_load(path.read_text(encoding="utf-8")) for path in paths]
if len(documents) == 1:
self.data = documents[0]
else:
self.data = {
"version": "+".join(str(item.get("version", "unknown")) for item in documents),
"source": {"files": [str(path) for path in paths]},
"scope": "combined_virtual_seat_capabilities",
"hardware_execution_allowed": all(
item.get("hardware_execution_allowed") is True for item in documents
),
"limitations": [
limitation
for document in documents
for limitation in document.get("limitations", [])
],
"messages": [
message
for document in documents
for message in document.get("messages", [])
],
}
self.messages = {}
for item in self.data["messages"]:
if item["name"] in self.messages:
raise MatrixError(f"Duplicate matrix message: {item['name']}")
self.messages[item["name"]] = item
@property
def hardware_execution_allowed(self) -> bool:
return self.data.get("hardware_execution_allowed") is True
def message(self, name: str) -> dict[str, Any]:
try:
return self.messages[name]
except KeyError as exc:
raise MatrixError(f"Unknown matrix message: {name}") from exc
def signal(self, message_name: str, signal_name: str) -> dict[str, Any]:
message = self.message(message_name)
for signal in message["signals"]:
if signal["name"] == signal_name:
return signal
raise MatrixError(f"Unknown signal {signal_name} in {message_name}")
def encode(self, message_name: str, values: dict[str, int]) -> dict[str, Any]:
message = self.message(message_name)
raw_value = 0
for signal_name, value in values.items():
signal = self.signal(message_name, signal_name)
width = int(signal["bit_length"])
if not isinstance(value, int) or value < 0 or value >= 1 << width:
raise MatrixError(f"Value {value!r} does not fit {signal_name}[{width}]")
start_bit = int(signal["start_bit"])
mask = ((1 << width) - 1) << start_bit
raw_value = (raw_value & ~mask) | (value << start_bit)
payload_size = int(message["minimum_payload_bytes"])
payload = raw_value.to_bytes(payload_size, byteorder="little", signed=False)
return {
"message": message_name,
"frame_id": f"0x{int(message['frame_id']):03X}",
"payload_size": payload_size,
"data_hex": " ".join(f"{byte:02X}" for byte in payload),
"signals": values,
"encoding": "lsb0/intel inferred for virtual execution",
}
def decode(self, message_name: str, payload: bytes) -> dict[str, int]:
"""Decode every declared little-endian lsb0 signal from a CAN payload."""
message = self.message(message_name)
minimum = int(message["minimum_payload_bytes"])
if len(payload) < minimum:
raise MatrixError(
f"Payload for {message_name} must contain at least {minimum} bytes"
)
raw_value = int.from_bytes(payload[:minimum], byteorder="little", signed=False)
return {
signal["name"]: (
raw_value >> int(signal["start_bit"])
) & ((1 << int(signal["bit_length"])) - 1)
for signal in message["signals"]
}
def decode_by_frame_id(self, frame_id: int, payload: bytes) -> dict[str, int]:
matches = [
item for item in self.data["messages"] if int(item["frame_id"]) == frame_id
]
if len(matches) != 1:
raise MatrixError(
f"Expected exactly one matrix message for frame 0x{frame_id:03X}"
)
return self.decode(matches[0]["name"], payload)
+71
View File
@@ -0,0 +1,71 @@
"""Read the supplied XLSX matrix using the standard OOXML structures."""
from __future__ import annotations
import re
import zipfile
from pathlib import Path
from typing import Any
from xml.etree import ElementTree
MAIN_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
CELL_REFERENCE = re.compile(r"([A-Z]+)([0-9]+)")
def _column_index(reference: str) -> int:
match = CELL_REFERENCE.fullmatch(reference)
if not match:
raise ValueError(f"Invalid XLSX cell reference: {reference}")
result = 0
for character in match.group(1):
result = result * 26 + ord(character) - ord("A") + 1
return result - 1
def _shared_strings(archive: zipfile.ZipFile) -> list[str]:
root = ElementTree.fromstring(archive.read("xl/sharedStrings.xml"))
return [
"".join(node.text or "" for node in item.iter(f"{{{MAIN_NS}}}t"))
for item in root.findall(f"{{{MAIN_NS}}}si")
]
def read_source_signals(path: Path) -> list[dict[str, Any]]:
"""Return the 23 signal rows needed for source-to-normalized contract checks."""
with zipfile.ZipFile(path) as archive:
strings = _shared_strings(archive)
worksheet = ElementTree.fromstring(archive.read("xl/worksheets/sheet1.xml"))
rows: list[list[str]] = []
for row_node in worksheet.iter(f"{{{MAIN_NS}}}row"):
values: dict[int, str] = {}
for cell in row_node.findall(f"{{{MAIN_NS}}}c"):
value_node = cell.find(f"{{{MAIN_NS}}}v")
if value_node is None or value_node.text is None:
continue
value = value_node.text
if cell.get("t") == "s":
value = strings[int(value)]
values[_column_index(str(cell.get("r")))] = value
if values:
width = max(values) + 1
rows.append([values.get(index, "") for index in range(width)])
records = []
for row in rows[1:]:
if len(row) < 6 or not row[0]:
continue
records.append(
{
"name": row[0],
"description": row[1],
"frame_id": int(row[2], 16),
"sub_id": None if row[3] in {"", "null"} else int(row[3], 16),
"start_bit": int(float(row[4])),
"bit_length": int(float(row[5])),
}
)
return records
+256
View File
@@ -0,0 +1,256 @@
"""Seat-specific semantic validation beyond JSON Schema."""
from __future__ import annotations
from typing import Any
SUPPORTED_TARGETS = {"driver", "passenger"}
SUPPORTED_FUNCTIONS = {"ventilation", "heating"}
SUPPORTED_STATES = {"on", "off"}
SUPPORTED_MOTION_AXES = {
"longitudinal",
"cushion_tilt",
"backrest_recline",
"legrest_height",
"legrest_extension",
}
ANGLE_RANGES = {
"backrest_recline": (95, 140),
"legrest_height": (95, 160),
}
def validate_semantics(
parsed: dict[str, Any], function_config: dict[str, Any] | None
) -> list[dict[str, str]]:
issues: list[dict[str, str]] = []
detected_domains = set(parsed.get("detected_domains", ["seat"]))
if parsed.get("actions") and "seat" not in detected_domains:
issues.append(
{
"severity": "error",
"code": "DOMAIN_ACTION_MISMATCH",
"path": "detected_domains",
"message": "GPT 生成了座椅动作,但 detected_domains 中没有 seat。",
}
)
for index, deferred in enumerate(parsed.get("deferred_requests", [])):
domain = str(deferred.get("domain"))
capability = str(deferred.get("capability"))
if domain not in detected_domains or not capability.startswith(f"{domain}."):
issues.append(
{
"severity": "error",
"code": "DEFERRED_REQUEST_MISMATCH",
"path": f"deferred_requests[{index}]",
"message": "暂缓请求的领域、能力名和 detected_domains 不一致。",
}
)
issues.append(
{
"severity": "warning",
"code": "UNAVAILABLE_DOMAIN",
"path": f"deferred_requests[{index}]",
"message": f"已理解 {capability} 目标,但 {domain} 域当前仅有能力目录,未接入执行器。",
}
)
for ambiguity in parsed.get("ambiguities", []):
issues.append(
{
"severity": "error",
"code": "AMBIGUOUS_INTENT",
"path": ambiguity["field"],
"message": ambiguity["question"],
}
)
seen: dict[tuple[str, str], str] = {}
active_functions: dict[str, set[str]] = {}
has_motion = False
has_driver_motion = False
for index, action in enumerate(parsed.get("actions", [])):
path = f"actions[{index}]"
if action.get("target") not in SUPPORTED_TARGETS:
issues.append(
{
"severity": "error",
"code": "UNSUPPORTED_TARGET",
"path": f"{path}.target",
"message": "当前只支持主驾和前排副驾。",
}
)
if action.get("action_type") == "position":
has_motion = True
has_driver_motion = has_driver_motion or action.get("target") == "driver"
axis = action.get("axis")
position = action.get("position_pct")
if axis not in SUPPORTED_MOTION_AXES:
issues.append(
{
"severity": "error",
"code": "UNSUPPORTED_MOTION_AXIS",
"path": f"{path}.axis",
"message": "请求的座椅位置轴未在当前能力目录中注册。",
}
)
if (
not isinstance(position, int)
or isinstance(position, bool)
or not 0 <= position <= 100
):
issues.append(
{
"severity": "error",
"code": "UNSUPPORTED_POSITION_RANGE",
"path": f"{path}.position_pct",
"message": "座椅电机绝对目标必须是 0-100 的整数百分比。",
}
)
angle = action.get("angle_deg")
if angle is not None:
angle_range = ANGLE_RANGES.get(str(axis))
if (
angle_range is None
or not isinstance(angle, (int, float))
or isinstance(angle, bool)
or not angle_range[0] <= float(angle) <= angle_range[1]
):
issues.append(
{
"severity": "error",
"code": "UNSUPPORTED_ANGLE_RANGE",
"path": f"{path}.angle_deg",
"message": "座椅夹角超出当前虚拟标定范围。",
}
)
key = (str(action.get("target")), str(axis))
previous_position = seen.get(key)
if previous_position is not None and previous_position != str(position):
issues.append(
{
"severity": "error",
"code": "CONFLICTING_OPERATIONS",
"path": path,
"message": "同一个座椅电机轴不能在一份配置中包含不同目标位置。",
}
)
seen[key] = str(position)
continue
if action.get("function") not in SUPPORTED_FUNCTIONS:
issues.append(
{
"severity": "error",
"code": "UNSUPPORTED_CAPABILITY",
"path": f"{path}.function",
"message": "当前只支持座椅通风和加热。",
}
)
if action.get("state") not in SUPPORTED_STATES:
issues.append(
{
"severity": "error",
"code": "UNSUPPORTED_STATE",
"path": f"{path}.state",
"message": "当前控制信号只支持打开或关闭。",
}
)
requested_level = action.get("requested_level")
if requested_level is not None and requested_level not in {1, 2, 3}:
issues.append(
{
"severity": "error",
"code": "UNSUPPORTED_LEVEL_RANGE",
"path": f"{path}.requested_level",
"message": "当前舒适推断和 0x4FA 虚拟回读仅支持 1-3 档。",
}
)
elif requested_level is not None:
issues.append(
{
"severity": "warning",
"code": "LEVEL_COMMAND_SIMULATED",
"path": f"{path}.requested_level",
"message": (
"目标档位已写入 FunctionConfig 和会话状态;"
"0x42A 仅编译开关信号,档位回读仅在 dry-run 中模拟。"
),
}
)
key = (str(action.get("target")), str(action.get("function")))
previous_state = seen.get(key)
if previous_state is not None and previous_state != action.get("state"):
issues.append(
{
"severity": "error",
"code": "CONFLICTING_OPERATIONS",
"path": path,
"message": "同一个座椅功能不能在一份配置中同时打开和关闭。",
}
)
seen[key] = str(action.get("state"))
if action.get("state") == "on":
active_functions.setdefault(str(action.get("target")), set()).add(
str(action.get("function"))
)
if has_motion:
issues.append(
{
"severity": "warning",
"code": "SYNTHETIC_MOTION_CAN",
"path": "platform.reference_vehicle.seat_motion",
"message": "座椅位置控制和反馈 CAN 矩阵完全为演示虚构,仅用于验证模型与配置链路。",
}
)
if has_driver_motion:
issues.append(
{
"severity": "warning",
"code": "DRIVER_MOTION_INTERLOCK_NOT_IMPLEMENTED",
"path": "platform.reference_vehicle.vehicle_state",
"message": "未接入车速、挡位、乘员和防夹状态,主驾移动只能虚拟执行。",
}
)
for target, functions in active_functions.items():
if functions == {"ventilation", "heating"}:
issues.append(
{
"severity": "warning",
"code": "COMBINED_THERMAL_SIMULATED",
"path": f"actions.{target}",
"message": (
"同一座位的加热与通风组合目标仅用于 dry-run;"
"实车前必须确认 ECU 是否允许两功能同时开启。"
),
}
)
if (
function_config is None
and not parsed.get("deferred_requests")
and not parsed.get("ambiguities")
and not parsed.get("preset_actions")
):
issues.append(
{
"severity": "error",
"code": "NO_EXECUTABLE_OPERATION",
"path": "spec.operations",
"message": "没有生成可执行的座椅操作。",
}
)
issues.append(
{
"severity": "warning",
"code": "VIRTUAL_EXECUTION_ONLY",
"path": "platform.reference_vehicle",
"message": "CAN 元数据不完整,当前仅允许虚拟执行,禁止实车下发。",
}
)
return issues
+4
View File
@@ -0,0 +1,4 @@
# Seat Tests
`unit/` 覆盖姿态参数和运动学转换,`contract/` 覆盖配置与插件契约,`integration/` 使用模拟 CAN Gateway 覆盖命令、回读、互锁和超时。实车范围需与平台安全标定分别测试,不能只依赖 JSON Schema 的宽范围。
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@

Some files were not shown because too many files have changed in this diff Show More