8d4a698144
Run the dev linters over Sprint 0's core/library/shared modules and address every finding. Behaviour unchanged; tests still 80/80 green. Changes: - Replace `class Foo(str, Enum)` with `class Foo(StrEnum)` (PEP 663 / Python 3.11+) in 7 enum classes: ActuatorType, AlarmSeverity, AlarmType, KnobMode, KnobFunction, AutopilotMode, AccessLevel, VesselType. Pydantic v2 serialises StrEnum the same way, so YAML/JSON round-trips are byte-identical. - Use `datetime.UTC` alias in place of `datetime.timezone.utc` (UP017) across alarms.py, knob_state.py, project_config.py, and test_knob_state.py. - Remove now-unnecessary forward-reference quotes from method return type annotations (UP037) — `from __future__ import annotations` is already in scope everywhere. - Tighten `_read_json_resource` / `_read_yaml_resource` in the library loader: validate that the deserialised payload is actually a dict before returning, instead of leaking `Any` from json.loads / yaml.safe_load. Fixes the only two `mypy --strict` findings. - Add `.claude/settings.local.json` to .gitignore (personal Claude Code overrides are not committed). Verification: ruff check arautopilot/ -> All checks passed mypy arautopilot/core library shared -> Success, 0 issues, 12 files pytest -> 80 passed in 0.25s Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
77 lines
2.1 KiB
Python
77 lines
2.1 KiB
Python
"""Autopilot operating modes.
|
|
|
|
The brief (section 3) defines five Phase 1 modes plus three Phase 2 sail
|
|
modes that appear greyed-out in the UI but are reserved here for forward
|
|
compatibility.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from enum import StrEnum
|
|
|
|
|
|
class AutopilotMode(StrEnum):
|
|
"""The complete set of autopilot operating modes, across both phases.
|
|
|
|
Use :func:`is_available_in_phase` to query phase gating instead of
|
|
hard-coding lists in the UI.
|
|
"""
|
|
|
|
# --- Phase 1 (Sprint 1+) ---
|
|
STANDBY = "standby"
|
|
"""Pilot disengaged, helm is manual."""
|
|
|
|
HEADING_HOLD = "heading_hold"
|
|
"""Holds a fixed magnetic/true compass heading."""
|
|
|
|
TRUE_COURSE = "true_course"
|
|
"""Holds COG, compensating drift due to current and wind."""
|
|
|
|
TRACK_KEEPING = "track_keeping"
|
|
"""Follows an ECDIS waypoint route with smooth XTE correction."""
|
|
|
|
DODGE = "dodge"
|
|
"""Temporary deviation without losing the route; auto-returns when released."""
|
|
|
|
# --- Phase 2 (Sprint 10+, greyed in Phase 1 UI) ---
|
|
APPARENT_WIND = "apparent_wind"
|
|
"""Holds constant apparent wind angle (vane mode). Sailboats only."""
|
|
|
|
TRUE_WIND = "true_wind"
|
|
"""Holds constant true wind angle. Sailboats only."""
|
|
|
|
AUTO_TACK = "auto_tack"
|
|
"""Automatically tacks at a target relative wind angle. Sailboats only."""
|
|
|
|
|
|
_PHASE_1: frozenset[AutopilotMode] = frozenset(
|
|
{
|
|
AutopilotMode.STANDBY,
|
|
AutopilotMode.HEADING_HOLD,
|
|
AutopilotMode.TRUE_COURSE,
|
|
AutopilotMode.TRACK_KEEPING,
|
|
AutopilotMode.DODGE,
|
|
}
|
|
)
|
|
|
|
_PHASE_2: frozenset[AutopilotMode] = frozenset(
|
|
{
|
|
AutopilotMode.APPARENT_WIND,
|
|
AutopilotMode.TRUE_WIND,
|
|
AutopilotMode.AUTO_TACK,
|
|
}
|
|
)
|
|
|
|
|
|
def is_available_in_phase(mode: AutopilotMode, phase: int) -> bool:
|
|
"""Return ``True`` if ``mode`` is available in the given product phase.
|
|
|
|
Phase 1 is the launch product. Phase 2 adds the sailboat wind modes.
|
|
Asking for any other phase number returns ``False``.
|
|
"""
|
|
if phase == 1:
|
|
return mode in _PHASE_1
|
|
if phase == 2:
|
|
return mode in _PHASE_1 or mode in _PHASE_2
|
|
return False
|