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

"""Стратегия слежения для ротатора с расширенным диапазоном азимута ±250°."""

from core.configs import logger
from soniks_client.antenna.tracking.base import TrackingStrategy


[документация] class Range250Strategy(TrackingStrategy): """ Ротатор с жёсткими границами по азимуту [-250, 250]. Сопровождение идёт «через 0»: если спутник движется 3° -> 2° -> 1° -> 0° -> 359°, ротатор продолжает движение -1° -> -2° -> -3° (а не разворачивается на 360°). Решение о направлении (в + или в -) принимается на каждой итерации по текущему положению ротатора: берётся значение sat_az, эквивалентное (mod 360) и ближайшее к текущему rot_az. """ LOWER_LIMIT = -250.0 UPPER_LIMIT = 250.0 def __init__(self, min_elevation: float, threshold: float) -> None: super().__init__(min_elevation, threshold) # Азимут упора, команда на который уже отправлена. Пока спутник за # пределами диапазона, ротатор всё равно стоит: повторять команду # каждые ROTATOR_UPDATE_INTERVAL до конца прохода бессмысленно. self._limit_commanded: float | None = None
[документация] def prepare_pass(self, aos_az: float, max_az: float, los_az: float) -> None: # Будущее улучшение: по aos/los можно заранее предсказать, пройдёт ли # спутник через 0 или через 180. Если через 180 — режим 250 невозможен, # можно залогировать предупреждение. """Предупредить, если пролёт идёт около юга и может оказаться недостижимым.""" self._limit_commanded = None if max_az is not None and 170 < max_az < 190: logger.warning( "[Range250] Спутник проходит около юга (max_az=%.1f°). " "Ротатор с границами ±250° может не успеть сопровождать.", max_az, )
[документация] def transform_target(self, sat_az, sat_alt, cur_rot_az, cur_rot_alt): # Переводим sat_az [0, 360) в непрерывную координату рядом с rot_az """Вернуть непрерывный азимут рядом с текущим положением, ограничив его аппаратными пределами ±250°. """ target_az = self.closest_equivalent(sat_az, cur_rot_az) # Жёсткий кламп в аппаратные пределы. Предупреждение — один раз на # упор, а не каждые ROTATOR_UPDATE_INTERVAL до конца прохода. limit = None if target_az < self.LOWER_LIMIT: limit = self.LOWER_LIMIT elif target_az > self.UPPER_LIMIT: limit = self.UPPER_LIMIT if limit is not None: if self._limit_commanded != limit: logger.warning( "[Range250] Целевой азимут %.1f° вне диапазона ±250°, " "ограничение до %.1f°.", target_az, limit, ) target_az = limit target_alt = self._clamp_min_elevation(sat_alt) return target_az, target_alt
[документация] def calculate_delta(self, target_az, target_alt, cur_az, cur_alt): # Здесь НЕТ взятия по модулю 360 — обе координаты уже в одной # непрерывной системе отсчёта (например, могут быть отрицательными). """Вернуть рассогласование без приведения по модулю 360. Обе координаты уже в одной непрерывной системе отсчёта и могут быть отрицательными. На упоре рассогласование по азимуту обнуляется после первой отправки: ротатор в предел уже упёрся, и дальше дельта остаётся большой до конца прохода, заставляя переотправлять одну и ту же команду. """ alt_delta = abs(target_alt - cur_alt) if target_az in (self.LOWER_LIMIT, self.UPPER_LIMIT): if self._limit_commanded == target_az: return 0.0, alt_delta self._limit_commanded = target_az else: self._limit_commanded = None return abs(target_az - cur_az), alt_delta