Исходный код 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 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