# Спутники и передатчики ## Satellite Космический аппарат в базе сети. | Поле | Назначение | |---|---| | `sat_id` | Идентификатор вида `CCCC-NNNN-NNNN-NNNN-NNNN` | | `norad_cat_id` | Номер в каталоге NORAD | | `name` | Название | | `status` | `alive`, `dead`, `future`, `re-entered` | | `source` | `Admin` (заведён вручную) или `CelesTrak` | | `operator` | Оператор (`Operator`) | | `network` | Сеть (`Network`) | | `mass` | Масса, **килограммы** | | `form_factor` | `3U`, `PocketQube`, `microsat` — свободный текст | | `sources` | `[{"title": …, "url": …}]`, откуда взяты паспортные данные | | `locked_fields` | Поля, которые импортёру запрещено перезаписывать | | `short_description_{ru,en,zh}` | Описание миссии одной строкой | | `description_{ru,en,zh}` | Развёрнутое описание миссии | ### Описания и языки Описание хранится по колонке на язык, а не словарём в JSON: точечная правка в админке тривиальна, блокировка перезаписи ложится на каждый язык отдельно, а поиск по описанию остаётся обычным `LIKE`. **Пустой язык — законное состояние.** Китайский может быть пуст всегда. Страница показывает описание на языке читателя, а если его нет — на английском, а если нет и его — на любом заполненном. Когда не заполнено ничего, блок описания **не рисуется вовсе**: заголовок над прочерком хуже отсутствующего блока. Сервиса перевода в портале нет и не будет. Перевод делается снаружи, а обмен идёт двумя командами — см. [](commands.md). ### Блокировка автоматической перезаписи `locked_fields` — список имён полей, которые импортёры не трогают. Это контракт между админкой и **всеми** источниками сразу, а не настройка одного скрейпера: любой импортёр обязан прочитать список и пропустить перечисленное. Набор полей, которые вообще можно заблокировать, задан константой `LOCKABLE_FIELDS` в `network/base/models.py`. Форма спутника в админке рисует поверх одной этой колонки блок чекбоксов «Не перезаписывать». Галочка ставится **вручную**: автоблокировки по факту правки нет намеренно. Отсюда цена, о которой надо помнить, — администратор, вычитавший описание и забывший отметить поле, потеряет свой текст на следующем обходе источника. ### Откуда берутся паспортные данные Два импортёра, оба уважают `locked_fields` и оба соединяются с каталогом **только по номеру NORAD** — подробности и расписание в [](tasks.md). | Источник | Что даёт | Лицензия | |---|---|---| | nanosats.eu | Наноспутники: масса, форм-фактор, описание одной строкой, ссылки на источники | Не объявлена — берём текст и числа, снимки не берём | | Wikidata | Масса, описания на трёх языках, ссылка на статью, файл на Commons | CC0 | | Wikimedia Commons | Сами фотографии с автором и лицензией | По файлу, читается через API | Ручной ввод и правка через админку доступны всегда и имеют приоритет: галочка «Не перезаписывать» замораживает поле для всех импортёров сразу. ### SatellitePhoto У спутника может быть несколько фотографий. `Satellite.image` при этом остаётся тем, чем был: одна картинка, которую пишет импорт из SatNOGS DB и публикует API. | Поле | Назначение | |---|---| | `image` | Файл; те же ограничения, что у прочих картинок — jpg/png/gif/webp, до 2 МБ | | `source_url` | Страница-первоисточник снимка, а не наша копия | | `author` | Автор снимка | | `license` | `PD`, `CC BY-SA 4.0`, пусто | | `caption`, `is_cover`, `order` | Подпись, признак обложки, порядок | Обложка выбирается так: отмеченная `is_cover` → первая по `order` → `Satellite.image` → заглушка. `source_url`, `author` и `license` заполняются обязательно и выводятся подписью под снимком. Это не оформление: ни nanosats.eu, ни Wikimedia Commons не отдают снимок безусловно — указание автора и лицензии есть условие, на котором его вообще можно показывать. Статус выставляется вручную — с единственным исключением, переходом `in orbit → re-entered` (см. ниже). Поэтому для практических решений (например, публичной выдачи TLE) портал опирается не только на него, но и на возраст последнего набора TLE — см. [](tle.md). Орбитальные параметры пересчитываются задачей `calculate_all_satellite_orbital` (ежедневно в 05:00 UTC), логика — в `network/base/orbital_satellite_params.py`. Сошедшие с орбиты аппараты отображаются отдельным представлением (`network/base/views/decay.py`). ### История орбиты На вкладке «Орбита» страницы спутника под строками TLE — график средних элементов по всем наборам TLE за 365 дней: высота большой полуоси над Землёй (`a − R⊕`), наклонение, эксцентриситет, долгота восходящего узла и аргумент перигея. По оси времени — эпоха набора из самих строк, а в выборку строки попадают по `Tle.updated`. Так сделано потому, что у строк, записанных до появления столбца `epoch`, он пустой. Наборы с эпохой старше года отбрасываются: источник может заново опубликовать последние элементы замолчавшего аппарата, и одна такая точка растянула бы ось на годы. Каждый источник (`Tle.tle_source`) рисуется своей линией, легенда скрывает лишние. Если источник молчал больше недели, линия рвётся, а не тянется через пропуск. Скачок высоты означает манёвр или ошибочный набор, а расхождение линий показывает разницу между источниками. Средней аномалии на графике нет: на эпоху набора это случайное число от 0 до 360°. Данные отдаёт `/orbit_history//` (`orbit_history` в `network/base/views/satellite.py`), кэш `satellite--orbit-history` на `CACHE_TTL`. Страница запрашивает их, только когда открыта вкладка. Набор разбирает `mean_elements` в `network/base/tle_utils.py`, а строки, которые не разбираются, пропускаются. История загрузок TLE хранится целиком, см. [](tle.md). Строка ответа — `[эпоха, a − R⊕, i, e, Ω, ω, updated, R, T, N]`: времена в миллисекундах, расхождения в километрах. Кнопка «Возраст TLE» строит пилу по каждому источнику. В момент `updated` очередного набора возраст равен `updated − эпоха` самого свежего набора, что источник прислал до этого момента, и растёт до прихода следующего. `updated` — время записи строки у нас, а не публикации у источника. `update_tle` не записывает набор, повторённый без изменений, поэтому повтор возраст не сбрасывает. На dev `prod_sync` переписывает `updated` временем синхронизации, и пила там ничего не значит. Шкала логарифмическая: у Celestrak возраст измеряется часами, а у источника, молчавшего месяц, — сотнями часов. ### Расхождение прогноза Второй график вкладки строится из того же ответа. Наборы одного источника сортируются по эпохе. Предыдущий набор продолжается SGP4 до эпохи следующего и сравнивается с состоянием следующего набора на его эпоху, оба в TEME. Разность раскладывается по осям следующей орбиты: R — радиус, T — вдоль орбиты (плюс, когда прогноз впереди), N — нормаль к плоскости. Расчёт — `prediction_error` в `network/base/tle_utils.py`. Пары дальше `PREDICTION_HORIZON_DAYS` (7 суток) не сравниваются, у первого набора источника расхождения нет, и в обоих случаях там `null`. Источники не смешиваются. Это согласованность наборов между собой, а не истинная ошибка: эталон — тоже TLE с ошибкой порядка километра, а ковариации в TLE нет, так что полос σ тоже нет. Манёвр между наборами виден как выброс: у МКС подъём орбиты даёт сотни километров вдоль орбиты за сутки, а обычное расхождение у Celestrak — метры–сотни метров. Поэтому шкала логарифмическая, по ней откладывается модуль расхождения, а знак виден в подсказке вместе с горизонтом прогноза. ### Автоматический перевод в «Сошёл с орбиты» Задача `confirm_satellite_reentries` (ежедневно в 06:00 UTC, логика — в `network/base/decay_confirmation.py`) переводит спутник из `in orbit` в `re-entered` и заполняет `decayed`. **По прогнозной дате она этого не делает никогда**: слежение часто ведут до последних сигналов, уточняя TLE уже после того, как прогнозная дата прошла, и такой аппарат должен остаться в планировании. Нужно одно из двух: * Space-Track опубликовал подтверждённый сход (`MSG_TYPE=Historical`; предсказательные TIP-сообщения приходят с `MSG_TYPE=Prediction` и игнорируются). Записывается реальная дата схода. * Ни один источник TLE не публиковал аппарат `DECAY_TLE_SILENCE_DAYS` дней **и** прогнозная `decay_date` уже прошла. Записывается эпоха последнего известного TLE — это нижняя граница. Правило работает и без учётки Space-Track. Спутники с синтетическим номером NORAD (диапазон 70000–100000, выдаётся `generate_norad_id`) по первому правилу не переводятся никогда: в каталоге Space-Track тот же номер принадлежит другому объекту. Каждый автоматический перевод пишется в `SatelliteStatusLog` (видно в админке): старый и новый статус, сработавшее правило, время. Ручные правки туда не попадают — они и так есть в истории объекта в админке. **Откат.** Если перевод оказался ошибочным, верните статус в админке. Строка в `SatelliteStatusLog` после этого навсегда запрещает автоматике переигрывать решение человека; чтобы снова её включить, удалите эту строку. У сошедшего аппарата остаются заполненными оба поля: `decayed` — подтверждённый факт, `decay_date` — замороженный прогноз, каким он был, пока приходили TLE. Вместе они дают вилку, поэтому прогноз намеренно не перезаписывается. Будущие наблюдения переведённого спутника не удаляются. Их количество печатает команда `confirm_satellite_reentries` (см. [](commands.md)), которая по умолчанию работает в режиме предпросмотра. ## Transmitter Передатчик спутника — то, на что фактически настраивается станция. | Поле | Назначение | |---|---| | `uplink_low`, `uplink_high` | Границы диапазона передачи «вверх» | | `downlink_low`, `downlink_high` | Границы диапазона передачи «вниз» | | `downlink_mode`, `uplink_mode` | Режим модуляции (`Mode`) | | `type` | `Transmitter`, `Transceiver`, `Transponder` | | `status` | `active`, `inactive`, `invalid` | | `iaru_coordination` | Статус координации IARU | Частоты валидируются в диапазоне от 0 Гц до 40 ГГц. Планирование сопоставляет `downlink_*` передатчика с диапазонами антенн станции — см. [](stations.md) и [](scheduling.md). ### Mode Справочник режимов модуляции (`Mode`). Загружается начальными фикстурами и пополняется из SatNOGS DB. Режим важен не только для приёма: от него зависит автоматическая оценка наблюдения — для аналоговых режимов (CW, FM) успешный приём кадра не означает «good», см. [](rating.md). ### TransmitterStats Агрегированная статистика по передатчику. Пересчитывается задачей `update_transmitters_stats` каждые 15 минут; кэшируемые расчёты — в `network/base/stats.py`. ## Telemetry `Telemetry` описывает декодеры телеметрии, применяемые к демодулированным данным спутника. (satellite-subscription)= ## Подписка на данные Пользователь подписывается на спутник кнопкой «Подписаться на данные» на его странице. После этого каждый новый кадр спутника приходит на сервер пользователя (например, в ЦУП) по MQTT, сразу после приёма и попытки декодирования. Подписка доступна владельцу станции, которая сейчас в сети. Пока ни одна его станция не в сети, кадры ему не приходят. Подписка при этом остаётся и снова заработает, когда станция выйдет на связь. Параметры подключения указаны в профиле, в блоке «Подписка на данные спутников»: | Параметр | Значение | |---|---| | Адрес | `wss://sonik.space/mqtt` (MQTT поверх WebSocket, порт 443) | | Логин | `subscriber-` | | Пароль | Ключ подписки из профиля. Это не API-ключ: он открывает только потоки кадров | | Топик | `satellites//frames`, по одному на спутник. Шаблоны `+` и `#` не принимаются | Каждое сообщение — один кадр, тот же JSON, что элемент `GET /api/demoddata/`: `demodulated_data` в HEX и декодированные поля в `payload_json`. Если у спутника нет декодера, `payload_json` — пустая строка. Транслируются кадры станций сети и SiDS; суточный импорт из SatNOGS не транслируется. Пропущенное за время отключения брокер хранит в постоянной сессии: подключайтесь с постоянным `client_id`, `clean_session=false` и QoS 1. Сессия живёт 7 дней, очередь ограничена 1000 сообщениями. Для большего разрыва есть REST: `GET /api/demoddata/?sat_id=…&start=…`. ```python import paho.mqtt.client as mqtt client = mqtt.Client( mqtt.CallbackAPIVersion.VERSION2, client_id="my-mission-control", # постоянный: по нему брокер хранит очередь transport="websockets", clean_session=False, ) client.username_pw_set("subscriber-42", "<ключ подписки>") client.ws_set_options(path="/mqtt") client.tls_set() client.on_connect = lambda c, *_: c.subscribe("satellites//frames", qos=1) client.on_message = lambda c, userdata, msg: print(msg.payload.decode()) client.connect("sonik.space", 443) client.loop_forever() ``` Устройство на стороне портала описано в [](api/endpoints.md), раздел «Подписка на данные спутника». ## Пополнение базы | Источник | Механизм | |---|---| | SatNOGS DB | Задача `fetch_data` (раз в час) и одноимённая management-команда — тянет спутники и передатчики с `DB_API_ENDPOINT` | | SatNOGS Network | Задача `parse_data_from_satnogs` (ежедневно в 01:00 UTC, только при `ENVIRONMENT=production`) — кадры за прошедшие UTC-сутки | | CelesTrak | Через TLE-конвейер, см. [](tle.md) | | Вручную | Через `/admin/`, `source` остаётся `Admin` | | Обнаружение | `check_unknown_satellites` — находит объекты, которых нет в базе | Ручной запуск обновления из SatNOGS DB — задача `manual_update_db`. ```{note} Если `db.satnogs.org` недоступен с хостинга, задайте `SATNOGS_DB_PROXY_URL` — прокси применяется только к запросам к API SatNOGS DB и SatNOGS Network. ``` ### Синхронизация с продом На тестовых стендах каталог отстаёт от sonik.space. Кнопка «Синхронизация с sonik.space» в списке спутников админки (везде, кроме `ENVIRONMENT=production`) ставит задачу `sync_from_prod` (`network/base/prod_sync.py`). В отличие от импорта SatNOGS она **перезаписывает** данные: спутники, передатчики и последний TLE каждого спутника становятся такими же, как на проде. * Источник — публичный API прода, без авторизации. Что API не отдаёт, то не меняется: описания, паспортные данные, `launch`, `trans_decode`, `params` передатчика. * Спутник сопоставляется по `sat_id`. Если такого нет — по NORAD вне синтетического диапазона, когда он у прода один: строка стенда получает `sat_id` прода. * **Дубли сливаются.** Лишняя строка стенда с тем же NORAD отдаёт спутнику с прода наблюдения, TLE, данные и передатчики, после чего удаляется. * Спутники и передатчики, которых на проде нет, остаются. Если прод занял их синтетический NORAD, они получают новый. * Последним TLE становится TLE прода с его `tle_source`. Следующий `fetch_tle` пересчитывает выбор по приоритетам стенда. * Всё пишется одной транзакцией, поэтому недоступный прод не оставит каталог наполовину обновлённым. Прогон занимает около минуты, счётчики пишутся в лог Celery. ## Операторы, сети и запуски * `Operator` — эксплуатирующая организация. * `Network` — сеть, к которой отнесён аппарат. * `Launch` — запуск: носитель, площадка, оператор, орбита, статус; `is_tracked` отделяет запуски сети от мирового календаря Launch Library 2. См. [](launches.md). * `SatelliteLaunch` — аппарат в составе запуска: тип миссии, статус. Планирование наблюдений сразу после запуска описано в [](scheduling.md).