优化了副驾座椅控制

This commit is contained in:
minghanchen
2026-08-25 11:39:41 +08:00
parent 33c92d1352
commit 9ac8fdface
34 changed files with 31714 additions and 289 deletions
@@ -43,18 +43,24 @@ MQTT_VALID_FD_PAYLOAD_LENGTHS = frozenset(
)
MQTT_SAFETY_FEEDBACK_IDS = frozenset({0x342, 0x4EB})
MQTT_VEHICLE_FEEDBACK_IDS = OBSERVABLE_IDS | frozenset({0x2F5, 0x37B})
# Frames accepted from the Android head unit are the seat-ota wire protocol.
# ``0x302`` is an internal compatibility frame synthesized below so the
# existing seat-domain state reader can consume the four firmware axes; it is
# deliberately not accepted as authoritative MQTT input.
MQTT_SEAT_WIRE_FEEDBACK_IDS = frozenset({0x6A0, 0x6A2})
MQTT_SEAT_FEEDBACK_IDS = frozenset({0x302, 0x6A2})
# CAN0 accepts both state formats currently emitted by the passenger-seat
# stack. The production firmware publishes its authoritative classic-CAN
# status directly on 0x301..0x306/0x4FA. Older head-unit builds expose the
# read-only seat-ota request/response exchange on 0x6A0/0x6A2; that exchange is
# retained and translated to an internal 0x302 position frame below.
MQTT_SEAT_NATIVE_FEEDBACK_IDS = frozenset(
{0x301, 0x302, 0x303, 0x304, 0x305, 0x306, 0x4FA}
)
MQTT_SEAT_WIRE_FEEDBACK_IDS = MQTT_SEAT_NATIVE_FEEDBACK_IDS | frozenset(
{0x6A0, 0x6A2}
)
MQTT_SEAT_FEEDBACK_IDS = MQTT_SEAT_NATIVE_FEEDBACK_IDS | frozenset({0x6A2})
MQTT_ACCEPTED_FEEDBACK_IDS = {
0: MQTT_SEAT_FEEDBACK_IDS,
1: MQTT_VEHICLE_FEEDBACK_IDS,
}
MQTT_APPROVED_CONTROL_IDS = {
0: frozenset({0x6A0}),
0: frozenset({0x29E, 0x3CE, 0x6A0}),
1: frozenset({0x1DE, 0x32B, 0x3FC, 0x431, 0x4C1, 0x4EF, 0x4F5}),
}
MQTT_OTA_SEAT_RUNTIME_OPCODES = frozenset({0x63, 0x64, 0x65, 0x66})
@@ -449,6 +455,39 @@ def _validate_ota_seat_runtime_control(data: bytes) -> None:
)
def _validate_native_seat_thermal_control(frame: CanFrame) -> None:
"""Validate the two runtime thermal frames implemented by the CAN0 firmware."""
if len(frame.data) != 8:
raise CanGatewayError("CAN0 passenger-seat thermal control requires DLC 8")
if frame.arbitration_id == 0x3CE:
mode = (frame.data[0] >> 4) & 0x03
if mode not in {1, 2} or frame.data[0] & 0xCF or any(frame.data[1:]):
raise CanGatewayError("CAN0 0x3CE thermal mode payload is invalid")
return
if frame.arbitration_id != 0x29E:
raise CanGatewayError("CAN0 native thermal frame ID is invalid")
fan_on = frame.data[0] & 0x03
fan_mode = (frame.data[0] >> 2) & 0x03
heating_on = (frame.data[0] >> 4) & 0x03
fan_level = frame.data[2] & 0x0F
heating_level = (frame.data[2] >> 4) & 0x0F
if (
fan_on not in {1, 2}
or fan_mode != 1
or heating_on not in {1, 2}
or fan_level not in range(4)
or heating_level not in range(4)
or (fan_on == 1 and fan_level == 0)
or (fan_on == 2 and fan_level != 0)
or (heating_on == 1 and heating_level == 0)
or (heating_on == 2 and heating_level != 0)
or frame.data[1] != 0
or any(frame.data[3:])
):
raise CanGatewayError("CAN0 0x29E thermal control payload is invalid")
class MqttCanTransport:
"""One process-wide MQTT connection serving snapshots and executor buses."""
@@ -633,7 +672,7 @@ class MqttCanTransport:
discriminator = (
frame.data[0]
if frame.channel == 0
and frame.arbitration_id == 0x302
and frame.arbitration_id in {0x302, 0x303, 0x304}
and frame.data
else 0
)
@@ -751,7 +790,15 @@ class MqttCanTransport:
initial_frames = tuple(
frame
for (seen_channel, _frame_id, _discriminator),
(captured_monotonic, frame) in sorted(self._latest_bus_frames.items())
(captured_monotonic, frame) in sorted(
self._latest_bus_frames.items(),
key=lambda item: (
0 if item[0][1] == 0x306 else
1 if item[0][1] == 0x4FA else
2,
item[0],
),
)
if replay_latest
and seen_channel == channel
and now - captured_monotonic <= self.freshness_s
@@ -772,8 +819,10 @@ class MqttCanTransport:
if frame.channel == 0 and (frame.is_fd or frame.bitrate_switch):
raise CanGatewayError("CAN0 passenger-seat control requires classic CAN")
_validate_headunit_command_frame(frame)
if frame.channel == 0:
if frame.channel == 0 and frame.arbitration_id == 0x6A0:
_validate_ota_seat_runtime_control(frame.data)
elif frame.channel == 0:
_validate_native_seat_thermal_control(frame)
with self._condition:
client = self._client
state = self._state