Files
AR-Autopilot/examples/sprint0_demo.py
T
alro65 700756c16f sprint-0: foundations -- data model, seed library, tests, demo
Initial commit. Delivers what the brief calls 'Sprint 0 - Foundations'
(see docs/AR_Autopilot_brief.md section 12):

- Complete repository structure (arautopilot package + firmware, display,
  installer, tools placeholders + docs).
- Core data model (Pydantic v2): modes, alarms, actuator config, PID
  config + gain scheduling, vessel config, knob state machine, project
  config with YAML/JSON serialisation.
- Seed library: 2 actuator profiles (hydraulic & electric DC reversible)
  and 2 default tunings (yacht motor planeo 30 m and 40 m). Conservative
  literature values, NOT the integrator's production tuning IP.
- Firmware skeleton: only src/hal/pinout.h with the 21 I/O contract for
  the AR-NMEA-IO v1.0 board. No drivers, no main loop.
- Studio stubs (real PySide6 app starts in Sprint 4).
- pytest suite (80 tests, all green): modes, alarms, actuator, PID
  (incl. gain interpolation and the +/-50% adaptive bound from brief
  section 6), vessel, knob state, project config, library loader,
  end-to-end roundtrip.
- examples/sprint0_demo.py - the acceptance demo from the brief.

Acceptance criteria met:
- pytest green (80/80)
- demo creates, saves (YAML + JSON), reloads, and verifies a full
  ProjectConfig using the seed library
- repository ready for tag `sprint-0-approved`

See CHANGELOG.md for the detailed scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 23:57:18 -04:00

129 lines
4.6 KiB
Python

"""Sprint 0 acceptance demo.
Runs end-to-end the workflow the brief calls out in section 12:
1. Build a ``ProjectConfig`` programmatically using the seed library.
2. Save it to disk (YAML and JSON).
3. Reload it from disk.
4. Verify the reloaded object matches the original.
5. Print a human-readable summary.
Usage::
python examples/sprint0_demo.py
"""
from __future__ import annotations
from pathlib import Path
from arautopilot.core import (
ProjectConfig,
VesselConfig,
VesselType,
interpolate_gains,
)
from arautopilot.library.loader import (
list_actuator_profiles,
list_default_tunings,
load_actuator_profile,
load_default_tuning,
)
from arautopilot.version import __version__
OUTPUT_DIR = Path(__file__).parent / "output"
def main() -> int:
print("=" * 78)
print(f" AR-Autopilot v{__version__} -- Sprint 0 acceptance demo")
print("=" * 78)
# ---- 1. Inspect what the seed library carries ---------------------------
print("\n[1] Seed library contents:")
for profile in list_actuator_profiles():
print(f" - actuator profile : {profile}")
for tuning in list_default_tunings():
print(f" - default tuning : {tuning}")
# ---- 2. Compose a project from seed assets ------------------------------
print("\n[2] Building project from seed library...")
actuator = load_actuator_profile("hydraulic_reversible")
pid = load_default_tuning("yacht_motor_planeo_30m")
vessel = VesselConfig(
name="M/Y Sprint Zero",
type=VesselType.YACHT_MOTOR_PLANEO,
length_m=30.0,
displacement_t=125.0,
max_speed_kn=28.0,
actuator=actuator,
pid=pid,
)
project = ProjectConfig(
client_name="Demo Shipyard S.L.",
project_name="Sprint 0 demonstrator",
notes=(
"Created by examples/sprint0_demo.py. Seed-library actuator + tuning "
"for a 30 m planing motor yacht. Conservative literature gains; not "
"the integrator's production tuning."
),
vessel=vessel,
)
# ---- 3. Save it to disk -------------------------------------------------
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
yaml_out = project.save_yaml(OUTPUT_DIR / "demo_project.yaml")
json_out = project.save_json(OUTPUT_DIR / "demo_project.json")
print(f"\n[3] Saved to:")
print(f" - YAML : {yaml_out}")
print(f" - JSON : {json_out}")
# ---- 4. Reload and verify integrity -------------------------------------
print("\n[4] Reloading from disk and verifying integrity...")
rebuilt_yaml = ProjectConfig.load(yaml_out)
rebuilt_json = ProjectConfig.load(json_out)
assert rebuilt_yaml == project, "YAML round-trip mismatch"
assert rebuilt_json == project, "JSON round-trip mismatch"
print(" OK -- both YAML and JSON reload to identical objects.")
# ---- 5. Show a human-readable summary -----------------------------------
print("\n[5] Project summary:")
print(f" project_id : {project.project_id}")
print(f" client : {project.client_name}")
print(f" project name : {project.project_name}")
print(f" vessel : {project.vessel.name}")
print(f" vessel type : {project.vessel.type.value}")
print(f" length / Vmax : {project.vessel.length_m:.1f} m / {project.vessel.max_speed_kn:.1f} kn")
print(f" actuator type : {project.vessel.actuator.type.value}")
print(f" rudder limit : +/-{project.vessel.actuator.max_rudder_angle_deg:.1f} deg"
f" at max {project.vessel.actuator.max_rate_dps:.1f} deg/s")
print(f" inner PID Kp/Ki/Kd: "
f"{project.vessel.pid.inner_loop_base.kp} / "
f"{project.vessel.pid.inner_loop_base.ki} / "
f"{project.vessel.pid.inner_loop_base.kd}")
print(f" outer PID Kp/Ki/Kd: "
f"{project.vessel.pid.outer_loop_base.kp} / "
f"{project.vessel.pid.outer_loop_base.ki} / "
f"{project.vessel.pid.outer_loop_base.kd}")
print(f" ROT feed-forward : {project.vessel.pid.rot_feedforward_gain}")
print(f" gain schedule pts : {len(project.vessel.pid.gain_schedule)}")
# Show interpolation working
print("\n Interpolated outer-loop gains by SOG:")
for sog in (3.0, 5.0, 10.0, 15.0, 20.0, 28.0, 35.0):
gains = interpolate_gains(project.vessel.pid.gain_schedule, sog)
print(
f" SOG = {sog:5.1f} kn -> "
f"Kp={gains.kp:.3f} Ki={gains.ki:.4f} Kd={gains.kd:.3f}"
)
print("\nDemo complete.")
return 0
if __name__ == "__main__":
raise SystemExit(main())