feat: complete dual-seat whole-vehicle orchestration

This commit is contained in:
vee
2026-08-12 15:12:40 +08:00
parent 32379a1573
commit 9ce64b3a01
62 changed files with 9741 additions and 3950 deletions
@@ -179,6 +179,7 @@ static uint8_t s_diagnosticPeriodicId = 0u;
static bool s_motorPeriodicPending = false;
static bool s_diagnosticPeriodicPending = false;
static bool s_dynamicStatusPending = true;
static bool s_firmwareIdPending = true;
static uint8_t s_systemAlive = 0u;
static uint8_t s_diagnosticAlive[SEAT_MOVE_MOTOR_COUNT];
static uint32_t s_nextStartMs = 0u;
@@ -186,6 +187,7 @@ static uint32_t s_nextSystemStatusMs = 0u;
static uint32_t s_nextMotorPollMs = 0u;
static uint32_t s_nextDiagnosticPollMs = 0u;
static uint32_t s_nextDynamicStatusMs = 0u;
static uint32_t s_nextFirmwareIdMs = 0u;
static uint8_t s_hilArmedCase = 0u;
static uint16_t s_hilArmToken = 0u;
static uint32_t s_hilArmExpiresMs = 0u;
@@ -242,6 +244,7 @@ static bool SeatMove_SendMotorStatus(uint8_t motor);
static bool SeatMove_SendDiagnostic(uint8_t motor);
static bool SeatMove_SendCurrentDiagnostic(uint8_t motor);
static bool SeatMove_SendDynamicStatus(void);
static bool SeatMove_SendFirmwareIdentity(void);
static void SeatMove_ServiceFeedback(void);
bool SeatMoveApplication_Init(void)
@@ -282,10 +285,12 @@ bool SeatMoveApplication_Init(void)
s_nextMotorPollMs = now;
s_nextDiagnosticPollMs = now;
s_nextDynamicStatusMs = now;
s_nextFirmwareIdMs = now;
s_systemStatusPending = true;
s_motorStatusPendingMask = SEAT_MOVE_ACTIVE_MOTOR_MASK;
s_diagnosticPendingMask = SEAT_MOVE_ACTIVE_MOTOR_MASK;
s_dynamicStatusPending = true;
s_firmwareIdPending = true;
return true;
}
@@ -1818,6 +1823,22 @@ static bool SeatMove_SendDynamicStatus(void)
data1);
}
static bool SeatMove_SendFirmwareIdentity(void)
{
/* Bytes 0..3 are ASCII "SMV8". Keeping the persistent-store protocol
* name in the identity makes accidental matches with unrelated bench
* firmware very unlikely without consuming an actuator command ID. */
uint32_t data0 = (uint32_t)0x53u |
((uint32_t)0x4Du << 8u) |
((uint32_t)0x56u << 16u) |
((uint32_t)0x38u << 24u);
uint32_t data1 = (uint32_t)SEAT_MOVE_FIRMWARE_REVISION |
((uint32_t)SEAT_MOVE_FIRMWARE_BUILD_ID << 8u) |
((uint32_t)SEAT_MOVE_MOTOR_COUNT << 16u) |
((uint32_t)SEAT_MOVE_CAN_PROTOCOL_VERSION << 24u);
return BspSeatMoveCan_Send(SEAT_MOVE_CAN_FIRMWARE_ID_ID, data0, data1);
}
static void SeatMove_ServiceFeedback(void)
{
uint32_t now = Motor_GetTimeMs();
@@ -1833,6 +1854,11 @@ static void SeatMove_ServiceFeedback(void)
s_dynamicStatusPending = true;
s_nextDynamicStatusMs = now + SEAT_MOVE_DYNAMIC_STATUS_PERIOD_MS;
}
if(SeatMove_TimeReached(now, s_nextFirmwareIdMs))
{
s_firmwareIdPending = true;
s_nextFirmwareIdMs = now + 500u;
}
if(!s_motorPeriodicPending &&
SeatMove_TimeReached(now, s_nextMotorPollMs))
{
@@ -1881,6 +1907,19 @@ static void SeatMove_ServiceFeedback(void)
}
return;
}
if(s_firmwareIdPending)
{
if(SeatMove_SendFirmwareIdentity())
{
s_firmwareIdPending = false;
g_seatMoveFeedbackTxCount++;
}
else
{
g_seatMoveFeedbackTxFailureCount++;
}
return;
}
for(motor = 0u; motor < SEAT_MOVE_MOTOR_COUNT; motor++)
{
uint8_t bit = (uint8_t)(1u << motor);
@@ -33,11 +33,14 @@
#define SEAT_MOVE_M0_DEFAULT_DUTY_PERCENT (90u)
#define SEAT_MOVE_M1_DEFAULT_DUTY_PERCENT (90u)
#define SEAT_MOVE_M2_DEFAULT_DUTY_PERCENT (90u)
#define SEAT_MOVE_M3_DEFAULT_DUTY_PERCENT (90u)
/* The connected loaded legrest leaves its hard stop more reliably at the
* empirically verified diagnostic PWM than at maximum PWM. It is selected
* only after a supervised endpoint-departure attempt produced no Hall edge. */
#define SEAT_MOVE_M3_BREAKAWAY_DUTY_PERCENT (60u)
#define SEAT_MOVE_M3_DEFAULT_DUTY_PERCENT (50u)
/* M3 is the loaded legrest linkage. The standalone angle-test firmware
* proves sustained Hall travel at 50% duty, while the former 90% command
* makes the connected actuator stop driving after roughly 70 pulses even
* though the ECU continues to request PWM. Keep normal and endpoint-
* departure motion on the same verified bench duty so absolute requests do
* not trip that internal/mechanical protection window. */
#define SEAT_MOVE_M3_BREAKAWAY_DUTY_PERCENT (50u)
/* A Hall target is discrete and the connected actuator can physically stop
* up to four pulses short because of its limit contact and gear backlash. If
@@ -79,6 +82,12 @@
#define SEAT_MOVE_CAN_DIAGNOSTIC_ID (0x303u)
#define SEAT_MOVE_CAN_CURRENT_DIAGNOSTIC_ID (0x304u)
#define SEAT_MOVE_CAN_DYNAMIC_STATUS_ID (0x306u)
/* Repository-image identity. The legacy four-bit revision in 0x301 is
* already at 15, so 0x307 carries an independently incremented build ID.
* This lets the headless runtime distinguish the current image from an older
* revision-15 image or from a temporary standalone motor-test image. */
#define SEAT_MOVE_CAN_FIRMWARE_ID_ID (0x307u)
#define SEAT_MOVE_FIRMWARE_BUILD_ID (16u)
/* The self-healing hook is accepted only by this bench-demo image and only
* after a short-lived token arm exchange. */
#define SEAT_MOVE_CAN_HIL_SELF_HEAL_REQUEST_ID (0x2F0u)
@@ -174,6 +183,7 @@
(SEAT_MOVE_CAN_DIAGNOSTIC_ID > 0x7FFu) || \
(SEAT_MOVE_CAN_CURRENT_DIAGNOSTIC_ID > 0x7FFu) || \
(SEAT_MOVE_CAN_DYNAMIC_STATUS_ID > 0x7FFu) || \
(SEAT_MOVE_CAN_FIRMWARE_ID_ID > 0x7FFu) || \
(SEAT_MOVE_CAN_HIL_SELF_HEAL_REQUEST_ID > 0x7FFu) || \
(SEAT_MOVE_CAN_HIL_SELF_HEAL_RESPONSE_ID > 0x7FFu))
#error "Seat movement CAN identifiers must be 11-bit standard IDs."
@@ -183,6 +193,10 @@
#error "The firmware revision must fit in the 0x301 high nibble."
#endif
#if (SEAT_MOVE_FIRMWARE_BUILD_ID > 0xFFu)
#error "The firmware build ID must fit in the 0x307 build byte."
#endif
#if (SEAT_MOVE_DEBUG_SESSION_BREG_INDEX >= SRSS_BACKUP_NUM_BREG)
#error "The selected debug-session backup register is not available."
#endif
@@ -3618,8 +3618,7 @@ static uint8_t Motor_CalibrationProbeMotion(
uint32_t *travelEdges)
{
uint32_t index = (uint32_t)mode;
uint32_t verifyEdges = (mode == MOTOR_MODE_3_LINEAR_TRACK) ?
2ul : MOTOR_HOME_PROBE_VERIFY_EDGES;
uint32_t verifyEdges = MOTOR_HOME_PROBE_VERIFY_EDGES;
uint32_t rawTotalStart;
uint32_t rawTotalEnd;
uint32_t savedIntrStatus;
@@ -3890,9 +3889,6 @@ uint8_t Motor_Calibrate(motor_mode_t mode,
uint32_t measuredEdges = 0ul;
uint32_t probeEdges = 0ul;
uint32_t preloadEdges = 0ul;
uint32_t homeProbeVerifyEdges =
(mode == MOTOR_MODE_3_LINEAR_TRACK) ?
2ul : MOTOR_HOME_PROBE_VERIFY_EDGES;
uint8_t probeAttempt = 0u;
#if (MOTOR_DIAGNOSTIC_LOG_ENABLE != 0u)
uint32_t firstNearPulses = 0ul;
@@ -3960,7 +3956,7 @@ uint8_t Motor_Calibrate(motor_mode_t mode,
firstNearEdges = measuredEdges;
#endif
if((status == MOTOR_API_SUCCESS) &&
(measuredEdges < homeProbeVerifyEdges))
(measuredEdges < MOTOR_HOME_PROBE_VERIFY_EDGES))
{
/* Do not turn a non-start into a fake Hall zero. A genuine
* already-near condition is proved by moving across several Hall
@@ -3969,11 +3965,11 @@ uint8_t Motor_Calibrate(motor_mode_t mode,
* cycle to leave a mechanical hard stop. Retry the departure,
* but still require real Hall edges before accepting the home.
* This improves endpoint breakaway without weakening the open
* Hall / disconnected-motor diagnostic. The loaded M3 linkage
* is accepted after two filtered raw Hall edges: the real bench
* repeatedly produces one complete Hall pulse before its bounded
* probe stops, while an open/disconnected Hall still produces
* zero. The other axes retain the four-edge proof. */
* Hall / disconnected-motor diagnostic. Keep the same four-edge
* proof on M3 as the other axes: accepting a single complete Hall
* pulse can make a temporarily stuck legrest look like a real
* mechanical endpoint and incorrectly rewrite its position to
* Hall=0 before the linkage is fully lowered. */
for(probeAttempt = 0u; probeAttempt < 3u; probeAttempt++)
{
status = Motor_CalibrationProbeMotion(
File diff suppressed because it is too large Load Diff
@@ -6,7 +6,10 @@ import json
import os
import time
import zcanpro
try:
import zcanpro
except ImportError: # protocol helpers are also reused by the headless runtime
zcanpro = None
SEAT_RX_IDS = (0x301, 0x302, 0x303, 0x304, 0x305, 0x306, 0x4FA)
@@ -138,6 +141,8 @@ def command_payload(opcode, motor, direction, sequence, param_a=0,
class HilSession(object):
def __init__(self, group_name):
if zcanpro is None:
raise RuntimeError("HilSession must run inside ZCANPRO")
self.group_name = group_name
self.stop_requested = False
self.results = []