Исходный код soniks_client.antenna.tracking.base

"""Контракт стратегии слежения и общая угловая математика.

Исправления, связанные с переходом через 0/360, вносите сюда, а не в
конкретную стратегию — иначе они достанутся одному режиму из трёх.
"""

from abc import ABC, abstractmethod


[документация] class TrackingStrategy(ABC): """ Контракт преобразования координат спутника в команды для ротатора. Каждая стратегия отвечает за: - подготовку к пролёту (например, решение о flip); - перевод (az_sat, alt_sat) -> (az_rot, alt_rot); - расчёт дельт между целью и текущей позицией; - решение, нужно ли двигаться. """ FULL_CIRCLE = 360.0 HALF_CIRCLE = 180.0 def __init__(self, min_elevation: float, threshold: float) -> None: self._min_elevation = min_elevation self._threshold = threshold # ---------- API ----------
[документация] @abstractmethod def prepare_pass(self, aos_az: float, max_az: float, los_az: float) -> None: """Подготовиться к пролёту по азимутам восхода, кульминации и захода."""
[документация] @abstractmethod def transform_target( self, sat_az: float, sat_alt: float, cur_rot_az: float, cur_rot_alt: float, ) -> tuple[float, float]: """Перевести координаты спутника в целевые координаты ротатора."""
[документация] @abstractmethod def calculate_delta( self, target_az: float, target_alt: float, cur_az: float, cur_alt: float, ) -> tuple[float, float]: """Вернуть рассогласование по азимуту и углу места в градусах."""
# ---------- Общие хелперы ----------
[документация] def needs_movement(self, az_delta: float, alt_delta: float) -> bool: """Превысило ли рассогласование порог, при котором стоит двигать антенну.""" return az_delta > self._threshold or alt_delta > self._threshold
[документация] @staticmethod def shortest_angular_delta(a: float, b: float) -> float: """Кратчайшая угловая разница a-b в диапазоне [-180, 180], возвращаем модуль.""" delta = (a - b) % TrackingStrategy.FULL_CIRCLE if delta > TrackingStrategy.HALF_CIRCLE: delta -= TrackingStrategy.FULL_CIRCLE return abs(delta)
[документация] @staticmethod def closest_equivalent(sat_az: float, ref_az: float) -> float: """ Вернуть значение, эквивалентное sat_az (mod 360), ближайшее к ref_az. Нужно для режима 250: 350° при ref=-10° даст -10°. """ delta = (sat_az - ref_az) % TrackingStrategy.FULL_CIRCLE if delta > TrackingStrategy.HALF_CIRCLE: delta -= TrackingStrategy.FULL_CIRCLE return ref_az + delta
# ---------- Логика elevation (общая) ---------- def _clamp_min_elevation(self, sat_alt: float) -> float: """Если спутник ниже MIN_ELEVATION, удерживаем ротатор на минимальном угле.""" if sat_alt < self._min_elevation: return self._min_elevation return sat_alt