sprint-4: Project Editor + .appack compiler + IRudderActuator HAL + dual sensor config
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>
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
"""Rudder angle sensor configuration.
|
||||
|
||||
Supports single and dual (redundant) sensor setups. The primary sensor
|
||||
is mandatory for closed-loop operation. The redundant sensor is optional
|
||||
but strongly recommended for Phase-1 marine deployments.
|
||||
|
||||
Sensor families supported in Phase 1:
|
||||
|
||||
- ``AS5048A_SPI`` — contactless magnetic absolute encoder, 14-bit, SPI.
|
||||
Recommended for new installations (brief session 2026-05-18).
|
||||
- ``POTENTIOMETER`` — resistive potentiometer 0-10 V or 4-20 mA.
|
||||
Supported for legacy actuators.
|
||||
|
||||
Both sensors use a polycarbonate linkage arm to the rudder stock, giving
|
||||
a proportional rotation to the rudder angle.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
class RudderSensorType(StrEnum):
|
||||
AS5048A_SPI = "as5048a_spi"
|
||||
"""AMS AS5048A contactless magnetic encoder — 14-bit, SPI interface.
|
||||
No mechanical contact; requires diametrically magnetised 6 mm magnet
|
||||
on the rotating shaft. Recommended for all new Phase-1 installations."""
|
||||
|
||||
POTENTIOMETER = "potentiometer"
|
||||
"""Resistive potentiometer with 0-10 V or 4-20 mA signal conditioning.
|
||||
Suitable for retrofit on existing actuators that already carry a pot."""
|
||||
|
||||
|
||||
class RudderSensorConfig(BaseModel):
|
||||
"""Configuration for one physical rudder angle sensor.
|
||||
|
||||
The ``full_scale_deg`` / ``zero_offset_deg`` pair maps the electrical
|
||||
range of the sensor to real rudder degrees. Calibration during
|
||||
commissioning (Sprint 7) will update these values in NVS.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid", validate_assignment=True)
|
||||
|
||||
type: RudderSensorType
|
||||
"""Physical sensor technology."""
|
||||
|
||||
label: str = Field(
|
||||
default="",
|
||||
max_length=60,
|
||||
description="Free-form label, e.g. 'Primary – rudder stock' or 'Redundant – actuator arm'.",
|
||||
)
|
||||
|
||||
# SPI-specific (ignored for POTENTIOMETER)
|
||||
spi_cs_gpio: int = Field(
|
||||
default=10,
|
||||
ge=0,
|
||||
le=39,
|
||||
description="ESP32 GPIO pin used as SPI chip-select for AS5048A.",
|
||||
)
|
||||
|
||||
# Calibration
|
||||
full_scale_deg: float = Field(
|
||||
default=35.0,
|
||||
gt=0.0,
|
||||
le=45.0,
|
||||
description="Rudder angle (degrees) corresponding to full electrical output.",
|
||||
)
|
||||
zero_offset_deg: float = Field(
|
||||
default=0.0,
|
||||
ge=-10.0,
|
||||
le=10.0,
|
||||
description="Offset applied after scaling to correct mechanical amidships misalignment.",
|
||||
)
|
||||
|
||||
# Cross-validation (used by dual-sensor arbitrator)
|
||||
divergence_alarm_deg: float = Field(
|
||||
default=3.0,
|
||||
gt=0.0,
|
||||
le=15.0,
|
||||
description="Alarm threshold: raise SENSOR_DIVERGE if |A - B| exceeds this value.",
|
||||
)
|
||||
divergence_failover_deg: float = Field(
|
||||
default=6.0,
|
||||
gt=0.0,
|
||||
le=20.0,
|
||||
description="Failover threshold: switch to the other sensor if |A - B| exceeds this.",
|
||||
)
|
||||
|
||||
|
||||
class DualRudderSensorConfig(BaseModel):
|
||||
"""Primary + optional redundant rudder sensor pair.
|
||||
|
||||
When ``redundant`` is ``None`` the system operates in single-sensor
|
||||
mode (feedback_required still enforced). When ``redundant`` is
|
||||
present the firmware enables cross-validation and automatic failover.
|
||||
|
||||
Divergence thresholds are taken from the *primary* sensor's config so
|
||||
there is one canonical place to tune them.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid", validate_assignment=True)
|
||||
|
||||
primary: RudderSensorConfig
|
||||
redundant: RudderSensorConfig | None = None
|
||||
|
||||
@property
|
||||
def has_redundancy(self) -> bool:
|
||||
return self.redundant is not None
|
||||
@@ -13,6 +13,7 @@ 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):
|
||||
@@ -60,3 +61,13 @@ class VesselConfig(BaseModel):
|
||||
|
||||
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.",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user