e4812e9b44
Cherry-pick of ab28cb7 onto main (was missing from main branch history). Python: - arautopilot/core/sensor_config.py: RudderSensorConfig + DualRudderSensorConfig (AS5048A SPI / potentiometer, dual with cross-validation thresholds) - arautopilot/core/vessel_config.py: add sensors: DualRudderSensorConfig field - arautopilot/studio/compiler/appack.py: .appack compiler (ZIP with manifest.json + project.yaml + firmware_config.h + install_notes.txt) - arautopilot/studio/editors/project_editor.py: full project config editor (vessel / actuator / sensors / PID, RBAC-gated fields, save/load .yaml/.json) - arautopilot/studio/main_window.py: wire ProjectEditorWidget into Project tab - tests: test_sensor_config.py (11 tests) + test_appack_compiler.py (10 tests) Firmware: - hal/rudder_actuator_iface.h: IRudderActuator abstract interface - hal/rudder_actuator_hydraulic.h: reversible hydraulic pump (LEDC PWM) - hal/rudder_actuator_electric.h: reversible DC motor + deadband compensation - hal/rudder_actuator_factory.h: build-time type selection via AR_ACTUATOR_TYPE Tests: 483 passed (was 462, +21 Sprint 4) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
74 lines
2.3 KiB
Python
74 lines
2.3 KiB
Python
"""Per-vessel configuration: identification + actuator + PID.
|
||
|
||
Composes the lower-level configs into one object that lives at the heart
|
||
of every ``ProjectConfig``.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from enum import StrEnum
|
||
|
||
from pydantic import BaseModel, ConfigDict, Field
|
||
|
||
from arautopilot.core.actuator_config import ActuatorConfig
|
||
from arautopilot.core.ids import VesselId, new_vessel_id
|
||
from arautopilot.core.pid_config import PidConfig
|
||
from arautopilot.core.sensor_config import DualRudderSensorConfig, RudderSensorConfig, RudderSensorType
|
||
|
||
|
||
class VesselType(StrEnum):
|
||
"""Vessel classes targeted by Phase 1 of the product (brief section 3)."""
|
||
|
||
YACHT_MOTOR_PLANEO = "yacht_motor_planeo"
|
||
"""Planing motor yacht, 30-40 m."""
|
||
|
||
YACHT_MOTOR_DESPLAZAMIENTO = "yacht_motor_desplazamiento"
|
||
"""Displacement motor yacht, 30-40 m."""
|
||
|
||
SAILBOAT_MOTOR = "sailboat_motor"
|
||
"""Sailing yacht under motor (no sail trim). Phase 1 only."""
|
||
|
||
FISHING_BOAT = "fishing_boat"
|
||
"""Fishing vessel, 30 m class."""
|
||
|
||
SMALL_FERRY = "small_ferry"
|
||
"""Small ferry, 30 m class."""
|
||
|
||
PATROL_BOAT = "patrol_boat"
|
||
"""Coastal patrol boat, 30 m class."""
|
||
|
||
|
||
class VesselConfig(BaseModel):
|
||
"""Identification, geometry, and control configuration of one vessel."""
|
||
|
||
model_config = ConfigDict(extra="forbid", validate_assignment=True)
|
||
|
||
vessel_id: VesselId = Field(default_factory=new_vessel_id)
|
||
name: str = Field(min_length=1, max_length=120)
|
||
type: VesselType
|
||
length_m: float = Field(gt=0.0, le=200.0, description="Length overall, metres.")
|
||
displacement_t: float = Field(
|
||
default=0.0,
|
||
ge=0.0,
|
||
le=10_000.0,
|
||
description="Loaded displacement, tonnes. 0 means unknown.",
|
||
)
|
||
max_speed_kn: float = Field(
|
||
gt=0.0,
|
||
le=80.0,
|
||
description="Maximum design speed over ground, knots.",
|
||
)
|
||
|
||
actuator: ActuatorConfig
|
||
pid: PidConfig
|
||
sensors: DualRudderSensorConfig = Field(
|
||
default_factory=lambda: DualRudderSensorConfig(
|
||
primary=RudderSensorConfig(
|
||
type=RudderSensorType.AS5048A_SPI,
|
||
label="Primary – rudder stock",
|
||
spi_cs_gpio=10,
|
||
)
|
||
),
|
||
description="Rudder angle sensor(s). Dual config enables cross-validation and failover.",
|
||
)
|