384 lines
14 KiB
Python
384 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""Create the OTA-interactive application source without modifying the reference."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
|
|
def replace_once(text: str, old: str, new: str, label: str) -> str:
|
|
count = text.count(old)
|
|
if (
|
|
count == 0
|
|
and "MOTOR_LEGREST_DEFAULT_DUTY_PERCENT" in text
|
|
and label
|
|
in {
|
|
"calibration explicit legrest duty adapter",
|
|
"calibration legrest duty setter adapter",
|
|
}
|
|
):
|
|
return text
|
|
if count != 1:
|
|
raise RuntimeError(f"{label}: expected one source anchor, found {count}")
|
|
return text.replace(old, new, 1)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--source-root", type=Path, required=True)
|
|
parser.add_argument("--project-root", type=Path, required=True)
|
|
parser.add_argument("--output-root", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
|
|
source_root = args.source_root.resolve()
|
|
project_root = args.project_root.resolve()
|
|
output_root = args.output_root.resolve()
|
|
output_src = output_root / "src"
|
|
if output_src.is_symlink():
|
|
output_src.unlink()
|
|
elif output_src.exists():
|
|
shutil.rmtree(output_src)
|
|
output_src.mkdir(parents=True, exist_ok=True)
|
|
|
|
original_main = source_root / "src" / "main_cm0plus.c"
|
|
if not original_main.is_file():
|
|
raise FileNotFoundError(original_main)
|
|
text = original_main.read_text(encoding="utf-8")
|
|
|
|
text = replace_once(
|
|
text,
|
|
'#include "motor_control.h"',
|
|
'''#include "motor_control.h"
|
|
#include "ota_can.h"
|
|
#include "ota_protocol.h"
|
|
#include "ota_enter_bootloader.h"
|
|
#include "ota_seat_state.h"
|
|
#include "seat_demo_compat.h"
|
|
|
|
#ifndef MOTOR_OTA_INTERACTIVE_TEST
|
|
#define MOTOR_OTA_INTERACTIVE_TEST (0u)
|
|
#endif
|
|
|
|
#ifndef MOTOR_OTA_SELF_COMPOSE
|
|
#define MOTOR_OTA_SELF_COMPOSE (0u)
|
|
#endif
|
|
|
|
#ifndef MOTOR_OTA_CALIBRATION
|
|
#define MOTOR_OTA_CALIBRATION (0u)
|
|
#endif
|
|
|
|
#if (MOTOR_OTA_CALIBRATION != 0u)
|
|
static void OtaCalibration_ServiceHook(void);
|
|
#elif (MOTOR_OTA_SELF_COMPOSE != 0u)
|
|
static void OtaSelfCompose_ServiceHook(void);
|
|
#endif''',
|
|
"OTA include adapter",
|
|
)
|
|
|
|
text = replace_once(
|
|
text,
|
|
"#define MOTOR_OPERATION_TIMEOUT_MS (60000ul)",
|
|
'''/* The reference calibration firmware allows each endpoint leg up
|
|
* to 180 seconds. Stall/endpoint detection remains independent and stops
|
|
* motion much earlier when appropriate. */
|
|
#define MOTOR_OPERATION_TIMEOUT_MS (180000ul)''',
|
|
"calibration operation timeout adapter",
|
|
)
|
|
|
|
text = replace_once(
|
|
text,
|
|
"#define SIMPLE_DIRECTION_TEST_ACTIVE_MS (3000ul)",
|
|
'''#if (MOTOR_OTA_INTERACTIVE_TEST != 0u)
|
|
#define SIMPLE_DIRECTION_TEST_ACTIVE_MS (60000ul)
|
|
#else
|
|
#define SIMPLE_DIRECTION_TEST_ACTIVE_MS (3000ul)
|
|
#endif''',
|
|
"interactive timeout adapter",
|
|
)
|
|
|
|
text = replace_once(
|
|
text,
|
|
'''#if (MOTOR_AUTOMATED_TEST_SCENARIO == MOTOR_TEST_SCENARIO_SIMPLE_DIRECTION)
|
|
#if ((SIMPLE_DIRECTION_TEST_ACTIVE_MS != 3000ul) || \\
|
|
(SIMPLE_DIRECTION_TEST_DUTY_PERCENT != 70ul) || \\
|
|
(MOTOR_PWM_DUTY_PERCENT != SIMPLE_DIRECTION_TEST_DUTY_PERCENT))
|
|
#error "The simple direction test requires exactly 3000 ms at 70 percent duty."
|
|
#endif
|
|
#endif''',
|
|
'''#if (MOTOR_AUTOMATED_TEST_SCENARIO == MOTOR_TEST_SCENARIO_SIMPLE_DIRECTION)
|
|
#if (MOTOR_OTA_INTERACTIVE_TEST != 0u)
|
|
#if ((SIMPLE_DIRECTION_TEST_ACTIVE_MS != 60000ul) || \\
|
|
(SIMPLE_DIRECTION_TEST_DUTY_PERCENT != 70ul) || \\
|
|
(MOTOR_PWM_DUTY_PERCENT != SIMPLE_DIRECTION_TEST_DUTY_PERCENT))
|
|
#error "The interactive direction test requires a 60 s safety timeout at 70 percent duty."
|
|
#endif
|
|
#else
|
|
#if ((SIMPLE_DIRECTION_TEST_ACTIVE_MS != 3000ul) || \\
|
|
(SIMPLE_DIRECTION_TEST_DUTY_PERCENT != 70ul) || \\
|
|
(MOTOR_PWM_DUTY_PERCENT != SIMPLE_DIRECTION_TEST_DUTY_PERCENT))
|
|
#error "The simple direction test requires exactly 3000 ms at 70 percent duty."
|
|
#endif
|
|
#endif
|
|
#endif''',
|
|
"interactive assertion adapter",
|
|
)
|
|
|
|
text = replace_once(
|
|
text,
|
|
'''#if (MOTOR_AUTOMATED_TEST_SCENARIO == MOTOR_TEST_SCENARIO_POSITION)
|
|
#define MOTOR_PWM_DUTY_PERCENT (70ul)
|
|
#elif (MOTOR_SIMPLE_DIRECTION_TEST_ENABLED != 0u)''',
|
|
'''#if (MOTOR_AUTOMATED_TEST_SCENARIO == MOTOR_TEST_SCENARIO_POSITION)
|
|
#define MOTOR_PWM_DUTY_PERCENT (70ul)
|
|
#elif (MOTOR_OTA_SELF_COMPOSE != 0u)
|
|
#define MOTOR_PWM_DUTY_PERCENT (80ul)
|
|
#elif (MOTOR_SIMPLE_DIRECTION_TEST_ENABLED != 0u)''',
|
|
"self-compose duty adapter",
|
|
)
|
|
|
|
text = replace_once(
|
|
text,
|
|
''' if(false == state->positionValid)
|
|
{
|
|
#if (MOTOR_SIMPLE_DIRECTION_TEST_ENABLED != 0u)
|
|
/* Scenario 5 intentionally does not calibrate absolute position.
|
|
* Scenarios 5 and 6 reuse scenario 3's Hall window and stall
|
|
* event path in either direction; an endpoint is safely treated
|
|
* as stall because these tests do not establish position. */
|
|
if((index == MOTOR_AUTOMATED_TEST_MODE) &&
|
|
(state->recentWindowPulses <= MOTOR_STALL_MAX_PULSES))
|
|
{
|
|
Motor_RequestEventFromIsr(index, MOTOR_EVENT_STALL);
|
|
}
|
|
#endif
|
|
continue;
|
|
}''',
|
|
''' if(false == state->positionValid)
|
|
{
|
|
#if ((MOTOR_SIMPLE_DIRECTION_TEST_ENABLED != 0u) || \\
|
|
(MOTOR_OTA_SELF_COMPOSE != 0u))
|
|
/* Direction tests and self-compose drive steps intentionally run
|
|
* before absolute endpoint calibration. Hall inactivity is a
|
|
* fail-closed stall for the selected test motor; self-compose may
|
|
* select any of the four validated motors at runtime. */
|
|
if((((MOTOR_OTA_SELF_COMPOSE != 0u) &&
|
|
(index < MOTOR_PROFILE_COUNT)) ||
|
|
((MOTOR_SIMPLE_DIRECTION_TEST_ENABLED != 0u) &&
|
|
(index == MOTOR_AUTOMATED_TEST_MODE))) &&
|
|
(state->recentWindowPulses <= MOTOR_STALL_MAX_PULSES))
|
|
{
|
|
Motor_RequestEventFromIsr(index, MOTOR_EVENT_STALL);
|
|
}
|
|
#endif
|
|
continue;
|
|
}''',
|
|
"self-compose uncalibrated stall adapter",
|
|
)
|
|
|
|
text = replace_once(
|
|
text,
|
|
''' if((false != state->speedControlEnabled) &&
|
|
(false == state->calibrating))
|
|
{
|
|
dutyPercent = state->speedInitialDutyPercent;
|
|
}''',
|
|
''' if(false != state->calibrating)
|
|
{
|
|
/* Validated endpoint-seek torque from N7Calibration-backup-0805. */
|
|
dutyPercent = (motorIndex == (uint32_t)MOTOR_MODE_3_LEGREST) ?
|
|
MOTOR_LEGREST_DEFAULT_DUTY_PERCENT : 80u;
|
|
}
|
|
else if(false != state->speedControlEnabled)
|
|
{
|
|
dutyPercent = state->speedInitialDutyPercent;
|
|
}''',
|
|
"calibration duty adapter",
|
|
)
|
|
|
|
text = replace_once(
|
|
text,
|
|
''' if(motorIndex == (uint32_t)MOTOR_MODE_3_LEGREST)
|
|
{
|
|
dutyPercent = MOTOR_LEGREST_FIXED_DUTY_PERCENT;
|
|
state->fixedDutyPercent = MOTOR_LEGREST_FIXED_DUTY_PERCENT;
|
|
state->speedControlEnabled = false;
|
|
}
|
|
else if(false != state->calibrating)''',
|
|
''' if((motorIndex == (uint32_t)MOTOR_MODE_3_LEGREST) &&
|
|
(MOTOR_OTA_CALIBRATION == 0u))
|
|
{
|
|
dutyPercent = MOTOR_LEGREST_FIXED_DUTY_PERCENT;
|
|
state->fixedDutyPercent = MOTOR_LEGREST_FIXED_DUTY_PERCENT;
|
|
state->speedControlEnabled = false;
|
|
}
|
|
else if(false != state->calibrating)''',
|
|
"calibration explicit legrest duty adapter",
|
|
)
|
|
|
|
text = replace_once(
|
|
text,
|
|
''' if(index == (uint32_t)MOTOR_MODE_3_LEGREST)
|
|
{
|
|
dutyPercent = MOTOR_LEGREST_FIXED_DUTY_PERCENT;
|
|
motorRuntime[index].speedControlEnabled = false;
|
|
}
|
|
motorRuntime[index].fixedDutyPercent = dutyPercent;''',
|
|
''' if((index == (uint32_t)MOTOR_MODE_3_LEGREST) &&
|
|
(MOTOR_OTA_CALIBRATION == 0u))
|
|
{
|
|
dutyPercent = MOTOR_LEGREST_FIXED_DUTY_PERCENT;
|
|
motorRuntime[index].speedControlEnabled = false;
|
|
}
|
|
motorRuntime[index].fixedDutyPercent = dutyPercent;''',
|
|
"calibration legrest duty setter adapter",
|
|
)
|
|
|
|
text = replace_once(
|
|
text,
|
|
''' savedIntrStatus = Cy_SysLib_EnterCriticalSection();
|
|
motorRuntime[index].maxHallCount = measuredPulses;
|
|
motorRuntime[index].positionHallCount = measuredPulses;
|
|
motorRuntime[index].positionValid = true;''',
|
|
''' savedIntrStatus = Cy_SysLib_EnterCriticalSection();
|
|
/* Endpoint travel validates the mechanism and measuredPulses is
|
|
* retained for diagnostics. Production position conversion uses
|
|
* the fixed N7 Base range and is never recalibrated at runtime. */
|
|
motorRuntime[index].maxHallCount =
|
|
motorProfiles[index].defaultMaxHallCount;
|
|
motorRuntime[index].positionHallCount =
|
|
motorProfiles[index].defaultMaxHallCount;
|
|
motorRuntime[index].positionValid = true;''',
|
|
"fixed production Hall range adapter",
|
|
)
|
|
|
|
text = replace_once(
|
|
text,
|
|
''' savedIntrStatus = Cy_SysLib_EnterCriticalSection();
|
|
motorServiceActive = false;
|
|
Cy_SysLib_ExitCriticalSection(savedIntrStatus);
|
|
}''',
|
|
''' savedIntrStatus = Cy_SysLib_EnterCriticalSection();
|
|
motorServiceActive = false;
|
|
Cy_SysLib_ExitCriticalSection(savedIntrStatus);
|
|
#if (MOTOR_OTA_CALIBRATION != 0u)
|
|
OtaCalibration_ServiceHook();
|
|
#elif (MOTOR_OTA_SELF_COMPOSE != 0u)
|
|
OtaSelfCompose_ServiceHook();
|
|
#endif
|
|
#if ((MOTOR_OTA_CALIBRATION != 0u) || (MOTOR_OTA_SELF_COMPOSE != 0u))
|
|
SeatDemoCompat_Service();
|
|
#endif
|
|
}''',
|
|
"OTA CAN service hook",
|
|
)
|
|
|
|
text = replace_once(
|
|
text,
|
|
"int main(void)",
|
|
'''#if ((MOTOR_OTA_INTERACTIVE_TEST != 0u) || \
|
|
(MOTOR_OTA_SELF_COMPOSE != 0u) || \
|
|
(MOTOR_OTA_CALIBRATION != 0u))
|
|
#include "ota_seat_runtime.inc"
|
|
#endif
|
|
|
|
#if (MOTOR_OTA_CALIBRATION != 0u)
|
|
#include "ota_calibration.inc"
|
|
#endif
|
|
|
|
#if (MOTOR_OTA_INTERACTIVE_TEST != 0u)
|
|
#include "ota_interactive_test.inc"
|
|
#endif
|
|
|
|
#if (MOTOR_OTA_SELF_COMPOSE != 0u)
|
|
#include "ota_self_compose.inc"
|
|
#endif
|
|
|
|
#if ((MOTOR_OTA_CALIBRATION != 0u) || (MOTOR_OTA_SELF_COMPOSE != 0u))
|
|
#include "seat_demo_compat.inc"
|
|
#endif
|
|
|
|
int main(void)''',
|
|
"OTA application main include adapter",
|
|
)
|
|
|
|
text = replace_once(
|
|
text,
|
|
'''#elif (MOTOR_SIMPLE_DIRECTION_TEST_ENABLED != 0u)
|
|
return SimpleDirectionTest_Run(driverInitOk);''',
|
|
'''#elif (MOTOR_SIMPLE_DIRECTION_TEST_ENABLED != 0u)
|
|
#if (MOTOR_OTA_INTERACTIVE_TEST != 0u)
|
|
return OtaInteractiveTest_Run(driverInitOk);
|
|
#else
|
|
return SimpleDirectionTest_Run(driverInitOk);
|
|
#endif''',
|
|
"interactive run adapter",
|
|
)
|
|
|
|
text = replace_once(
|
|
text,
|
|
'''#if (MOTOR_AUTOMATED_TEST_SCENARIO == MOTOR_TEST_SCENARIO_FIVE_MODE_HALL)
|
|
return FiveModeTest_Run(driverInitOk);''',
|
|
'''#if (MOTOR_OTA_CALIBRATION != 0u)
|
|
return OtaCalibration_Run(driverInitOk);
|
|
#elif (MOTOR_OTA_SELF_COMPOSE != 0u)
|
|
return OtaSelfCompose_Run(driverInitOk);
|
|
#elif (MOTOR_AUTOMATED_TEST_SCENARIO == MOTOR_TEST_SCENARIO_FIVE_MODE_HALL)
|
|
return FiveModeTest_Run(driverInitOk);''',
|
|
"self-compose run adapter",
|
|
)
|
|
|
|
(output_src / "main_cm0plus.c").write_text(text, encoding="utf-8", newline="\n")
|
|
|
|
header_source = source_root / "src" / "motor_control.h"
|
|
header_text = header_source.read_text(encoding="utf-8")
|
|
# The vendored base is now the single authoritative physical mapping.
|
|
# Refuse to build if 01/03 are ever silently crossed again.
|
|
expected_axis_definitions = (
|
|
"MOTOR_MODE0_MAX_POSITION_MM_X100 (21500ul)",
|
|
"MOTOR_MODE1_DEFAULT_MAX_HALL_COUNT (373ul)",
|
|
"MOTOR_MODE1_MAX_POSITION_MM_X100 (5500ul)",
|
|
"MOTOR_MODE2_DEFAULT_MAX_HALL_COUNT (917ul)",
|
|
"MOTOR_MODE2_MAX_ANGLE_DEG_X100 (6800ul)",
|
|
"MOTOR_MODE3_DEFAULT_MAX_HALL_COUNT (647ul)",
|
|
"MOTOR_MODE3_MAX_ANGLE_DEG_X100 (7200ul)",
|
|
"MOTOR_MODE_1_HEIGHT",
|
|
"MOTOR_MODE_3_LEGREST",
|
|
)
|
|
missing = [value for value in expected_axis_definitions if value not in header_text]
|
|
if missing:
|
|
raise RuntimeError(
|
|
"N7 axis mapping drifted; missing: " + ", ".join(missing)
|
|
)
|
|
(output_src / "motor_control.h").write_text(
|
|
header_text, encoding="utf-8", newline="\n"
|
|
)
|
|
|
|
copies = {
|
|
project_root / "firmware" / "application" / "ota_interactive_test.inc": "ota_interactive_test.inc",
|
|
project_root / "firmware" / "application" / "ota_self_compose.inc": "ota_self_compose.inc",
|
|
project_root / "firmware" / "application" / "ota_seat_runtime.inc": "ota_seat_runtime.inc",
|
|
project_root / "firmware" / "application" / "ota_calibration.inc": "ota_calibration.inc",
|
|
project_root / "firmware" / "application" / "ota_seat_state.h": "ota_seat_state.h",
|
|
project_root / "firmware" / "application" / "seat_demo_compat.h": "seat_demo_compat.h",
|
|
project_root / "firmware" / "application" / "seat_demo_compat.inc": "seat_demo_compat.inc",
|
|
project_root / "firmware" / "application" / "seat_demo_dynamic.inc": "seat_demo_dynamic.inc",
|
|
project_root / "firmware" / "application" / "seat_demo_thermal.inc": "seat_demo_thermal.inc",
|
|
project_root / "firmware" / "application" / "ota_self_compose_plan.h": "ota_self_compose_plan.h",
|
|
project_root / "firmware" / "bootloader" / "src" / "ota_can.h": "ota_can.h",
|
|
project_root / "firmware" / "common" / "ota_protocol.h": "ota_protocol.h",
|
|
project_root / "firmware" / "application" / "ota_enter_bootloader.h": "ota_enter_bootloader.h",
|
|
}
|
|
for source, name in copies.items():
|
|
if not source.is_file():
|
|
raise FileNotFoundError(source)
|
|
shutil.copyfile(source, output_src / name)
|
|
|
|
print(f"GCC_APPLICATION_SOURCE_OK path={output_root}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|