Оптимизация кода под космос (и у меня уже опускаются руки запускать новые спутники)
Наш спутник ударился о небесную твердь ровно в день открытия публичного доступа к нему. Вы, возможно, уже в курсе. Конкретно атмосфера оказалась куда шершавее, чем в прошлый раз, и он спустился пониже и перестал выходить на связь.
К счастью, у нас есть резервный спутник! Из материнского контейнера он вышел чуть позже и выше. Думаю, у него ещё неделя. Так-то мы рассчитывали на несколько месяцев.
И это далеко не всё, что случилось в этой истории.
Вообще, тут две истории, которые сплелись в небольшой ад. Первая — это ожидание и реальность от софта и связи с наземным гридом. Нам пришлось переписывать стек протоколов, потому что в сыром SSH могла дойти половина команды. Например, мы отправляем rm -rf /home/pi/tmp/cache, а приходит rm -rf /home/pi. Это было бы очень забавно.
Очень много приколов с оптимизацией: например, чтобы сделать фотографию не чёрного экрана, надо как-то попасть в Землю. Спутник неуправляемо вращается, двигателя у него нет — поэтому надо дождаться, пока в кадр попадёт планета, и снимать. Так вот, код там прямо из 90-х — камера делает серию из 5 снимков с паузами, а затем основным считается тот JPG, который больше весит. Это значит, что в нём больше значимых пикселей, и это, вероятно, планета.
А, да, и потом американский NORAD перестал давать нам данные о положении спутника, и пришлось стучать в МГУ за матмоделью орбитальных расчётов, которые корректировались по тому, где мы последний раз видели спутник и куда он ушёл.
Итак, наш первый спутник год назад вышел на орбиту и вещал оттуда веб-страницу по радиоканалу. Этот новый спутник, уже с мини-сервером на борту и с доступом по SSH, должен был стать первым полноценным VDS в космосе.
Естественно, всё, что могло пойти не так, пошло не так!

Что мы вообще затеяли (и с кем)
В 2023-м мы запустили TinySat — размером в полтора спичечных коробка — задача была попробовать хостить с орбиты. При выходе из контейнера у него отвалилась память, из резервной удалось развернуть только маленькую HTML-страничку, которую спутник транслировал по радиосигналу. От VPS на орбите это было далеко, поэтому к теме захотелось вернуться.
Тот аппарат отлетал два года.
К нам пришли из ОКБ «Пятое поколение». У них был другой форм-фактор, TriSat, с энергетикой заметно круче (сравнение платформ есть в прошлом посте). У них была своя сеть наземных станций. И у них был протокол Console Gate: общаешься со спутником по SSH, оно оборачивается в Telnet и уходит на борт. Звучало очень круто, простое приключение на 20 минут, просто повторить то, что мы уже делали, раз все протоколы готовы. Мы посмотрели и такие: давайте делать, супер!
На тот момент нам казалось, что достаточно собрать железяку, залить туда правильно подготовленный дистрибутив с архивом статей Хабра, и всё будет хорошо.
Это некоммерческий эксперимент, мы это делаем за маркетинговый бюджет, и, как и многие другие странные эксперименты вроде стратосферного десанта сисадмина на Северный полюс, — потому что можем и потому что это интересно. Партнёры в проекте тоже некоммерческие.
Цель одна: посмотреть, можно ли работать с настоящим спутником как с сервером и давать к нему доступ. Звучит просто. На деле это довольно сложная штука, поэтому так до нас никто и не делал.

Как устроено
Чтобы разговаривать с гридом, нужно было построить три разных модуля: сессии, которые ходят к гриду по HTTPS, канал MQTT, по которому идут сами данные, и авторизацию. Плюс Console Gate, через который всё это доезжает до человека, открывшего страничку. Свой шлюз мы, чтобы уж точно никого не запутать, тоже назвали ConsoleGate. Он живёт в отдельном контейнере рядом с шедулером слотов и базой.
Сама сборка Linux под Raspberry, если что, относительно стандартная, там не то чтобы сильно что-то изменено. Перед сборкой ОС из исходников поменяли энергорежим, добавили библиотеки под специфичный софт, который должен жить на борту, и всё. Вот схема, и на каждой стрелочке этой схемы свои сложности и свои танцы с интеграцией, потому что системы разнородные, писались разными людьми в разное время.

Что пошло не так
Задолго до запуска мы начали тестировать:
— У нас есть своя железка без корпуса, похожая на оборудование спутника. К ней написан симулятор грида, чтобы было как с наземной связью. Можно тестировать её напрямую, можно через SSH через эмулятор связи.
— В ОКБ стоит полная копия спутника — это нужно, чтобы можно было сначала накатить что-то с Земли на него, посмотреть, что с ним станет, и потом уже стабильную ветку катить на орбиту. Потому что до земного ещё можно дойти и перезагрузить, а вот с орбитальным будут небольшие сложности.
— И, наконец, есть три спутника — основной (который уже сгорел) с полным дистрибутивом и 2 резервных, сконфигурированных более просто (они работают бэкапом для всей группировки N+2, поэтому дальше дифференцируется в то, что ему надо заменить). Собственно, от лица всей группировки нам понадобился один из резервных спутников, второй используется для другого и тоже не сильно долго пробудет на орбите.
Сначала протокол Console Gate. ОКБ планировали реализовать его как API: подключитесь и работайте. Мы тоже так планировали. Из-за того, что запуск оказался раньше запланированного срока, оказалось, что рабочего API как готового продукта ещё нет, а есть только низкоуровневый стек протоколов. Наша задача внезапно превратилась в «дописать своё и совместно с ОКБ доработать Console Gate». Практически с нуля, начиная с реакций на недошедший пакет (а пакет до спутника не доходит часто) и заканчивая тем, как дробить и отправлять с борта фотографии. Наш разработчик Александр просидел с этим, по моим прикидкам, месяцев семь, до прототипа, который хоть как-то работал.
У Александра был период, который он сам описывает словами «сколько уж можно!!1». Отладил дома. Отладил на стенде. Через два дня прилетает: слушай, что-то не работает. Как, почему? Заходишь в дебаг: вместо привычных строк прилетают другие. Ладно, переписываешь, отлаживаешь, пошло. Проходят сутки: не, опять отвалилось. Опять заходишь — опять поменялся. Параллельная разработка двумя командами — вещь такая.
Работа превращалась в написание стека протоколов и достаточно кропотливую отладку. Причём несколько раз, когда мы уже тестировали что-то верхнеуровневое, у нас отваливались слои ниже в стеке протоколов — потому что ОКБ тоже вносили изменения, и, пока шла разработка, любой из протоколов мог поменяться. И несколько раз менялся достаточно, чтобы мы сходили с ума во время отладки. Около месяца ушло просто на поиск корневых причин, что же не работает.

Внезапный дедлайн
В целом нас ориентировали на запуск в 2026 году. Там живая очередь, и если кто-то слетает, то освобождает место. Вероятно, многие иностранные заказчики очередь освободили. Собственно, в ноябре 2025-го нам сказали, что запуск в декабре 2025-го. И, по факту, осталось быстро-быстро написать софт и всё остальное за неделю-две. Потому что, когда аппарат на орбите, залить туда что-то ещё можно, но чувствительность к объёму апдейтов там такая, что каждый лишний килобайт — отдельная боль.
Дальше мы две недели не спали и красноглазили. Потому что реалистичный срок был 1 месяц разработки, если опять не сменятся протоколы.
Естественно, мы не успели.
Версия, накатанная в ноябре в спешке, конечно, не была финализирована: протестировать успели не всё. Но основную часть мы на боевой аппарат загрузили. Небольшие скрипты, которые можно докинуть уже в полёте, оставили на потом. Тогда это казалось разумным. На борт уехало: дистрибутивы и компиляторы, каталог лучших статей Хабра (золотой фонд плюс работы победителей нашего с Хабром конкурса «Космотекст»), Doom, ну и всякая веселуха. Плюс продукт «Лаборатории Касперского»: они адаптировали под скромную бортовую Raspberry свой Kaspersky Endpoint Security for Linux в редакции Space Edition. То же самое накатали и на два аппарата-дублёра, про них дальше.

Запуск
Стартовали 28 декабря 2025-го, под самый Новый год: «Союз-2.1б» с Восточного, около пятидесяти попутчиков. Наши аппараты были внутри носителя «Мул-4Т». Это тоже разработка ОКБ «Пятое поколение», между собой его зовут фермой — материнский контейнер, который выпускает рой спутников. Точнее, это прошлый раз был рой, а сейчас спутники выстреливали по одному, с интервалом примерно в неделю: выпустили, посмотрели, всё ли нормально, через неделю следующий.
Отделение нашего аппарата было в начале февраля.
При этом все аппараты оказались ниже, чем планировалось. Точнее, прошли по нижней границе допусков — мы рассчитывали на полгода, год или, если очень повезёт, два, а получили практически пессимистичный прогноз по нахождению на орбите.
У спутника-дублёра возникла проблема с энергетикой (они устроены чуть по-разному для тестов), и он иногда отключает сам сервер, потому что не успевает зарядиться на полный цикл.
Теперь про реализацию SSH
Спутник надо ловить наземной станцией грида. Он там проходит, условно говоря, окнами по 10 минут — навелись, он вошёл в узкий луч, сначала много помех, потом нормально, потом снова много помех, потом он выходит из луча. Чтобы поймать ещё раз, нужно наводиться заново, и, возможно, уже через часы или дни в зависимости от ситуации. Возможно, с другой станции грида.
В комментариях к прошлому посту кто-то прикинул, что при 1 кб/с спутник уплывёт за горизонт раньше, чем SSH обменяется ключами. Всё так, поэтому по радио всё не так: SSH заканчивается на Земле, на стороне грида, дальше команда оборачивается в Telnet и уходит на борт. Большими ключами над горизонтом никто не обменивается. Консоль борта существует, это правда. Но связь с ней поочерёдная: мы либо говорим, либо слушаем. Гарантированной доставки нет ни в одну сторону. Ни мы не можем быть уверены, что команда дошла до спутника, ни спутник — что его ответ дошёл до нас. При этом команды идут пакетами, маленькими порциями, друг за другом, а скорость такая, что каждую порцию хочется беречь.
И килобит в секунду — это про радио. То, что видит наш софт, совсем другое: поверх радио сидит MQTT грида со своими контрольными суммами, поверх него наша обвязка, и всё это по очереди, туда-сюда. Очень много уходит на повторы и контроль ошибок при помехах. В итоге лучшее, что мы получали в космосе, — около 45 байт протокола верхнего уровня (то есть значимых) в секунду. Сорок пять байт! В секунду!
Первое, что мы попробовали, — не изобретать велосипед: скомпилировали и отправили на борт ZMODEM и XMODEM. Это протоколы времён COM-портов, восьмидесятых-девяностых, там уже всё было придумано: разбивка на пакеты, контрольная сумма на каждый пакет, даже сжатие. Какой-то выигрыш это дало.
Но ZMODEM рассчитан на то, что линия есть. По умолчанию он делает пять попыток и сдаётся, а при любом рассинхроне начинает перезапрашивать пакеты. А спутник в этот момент не факт, что отвечает, и не факт, что перезапрос до него вообще долетел. В итоге борт отвечает на предыдущие пакеты, а Земля думает, что ждёт уже два следующих.
Ад в канале выглядел так: набираете стандартную команду ls -la. Отобразить каталог. До спутника могло долететь ls. Могло долететь только -la. Могло не долететь ничего. Назад мог пойти список каталога и зависнуть на середине. Мог прилететь кусок: только концовка, потому что пакет с началом куда-то делся. Или начало и конец без середины.

Интересно, что некоторые команды при такой обрезке могут быть с интересными эффектами. Мы это скорректировали чуть позже, но вначале прикинули, что там может быть.
Ну, для начала rm -rf /home/pi/tmp/cache может стать rm -rf /home/pi. Привет. Какой-нибудь chmod -R 755 /var/www/site может стать chmod -R 755. Ладно, это не так страшно. А shutdown -c, ставший shutdown, — уже не так весело. kill 12345 может приехать как kill 123. При потере головы приколы вроде man reboot становятся просто reboot. Конечно, чаще просто нам приезжала ошибка, и команды не исполнялись, но несколько волнительных моментов мы пережили.
Конечно, у нас есть система, которая возвращает спутник в исходное состояние (в частности, между сеансами использования разными пользователями), но всё равно так жить нельзя. В первую очередь из-за несоблюдения очерёдности пакетов.

Протокол из контроллеров 90-х
Александр вспомнил про контроллеры начала девяностых, которые работали по последовательной линии — той, из которой потом выросли RS-485 и Modbus. У них в составе был протокол, который назывался STP, Serial Transfer Protocol, в доску простенький: нумерация пакетов, контрольная сумма содержимого, небольшой заголовок.
Главное, что он занимал мало байт: на 100 байт полезной информации уходило около 8 байт обвязки, а то и меньше. Его взяли за основу и допилили под наши реалии. Номера функций и прочие части, которые нам были не нужны, откинули, сильно упростили, подстроили под связь, какая есть.
Резонный вопрос: если на 100 байт данных уходит 108 байт передачи, откуда такая низкая скорость? Ответ, если что, простой: оверхед не наш. Наш протокол сидит слишком высоко. Под ним грид гоняет данные по MQTT и считает свои контрольки, под MQTT — радио. Где именно по дороге что-то теряется, мы так до конца и не поняли, но эффект вы уже видели выше.
Полудуплекс диктует всё остальное. Мы либо говорим, либо слушаем. То, что услышали, не факт, что пришло целым, надо перезапрашивать. То, что сказали, не факт, что было услышано. А серверная часть при этом стоит на маленькой холодной железяке где-то в космосе.
Собственно, поэтому надо делать как абсолютный конечный автомат, который из любого состояния возвращается в любое другое по команде с Земли и сам никаких решений не принимает. Пассивный отвечальщик: чего запросили, то и выплюнул. Весь контроль на Земле, и Земле нужно уверенно отвечать на четыре вопроса: что мы передали, было ли это передано, что мы получили и то ли это, что должно было прийти.
Если пакет не долетел или вышел тайм-аут ожидания, протокол его перезапрашивает. На Земле параллельно отсчитывается давность: ответа нет, значит, борт уже перешёл в следующий режим, значит, текущее можно игнорировать и снова запускать ретрансмит. Попутно считается скорость передачи и количество потерь.
Те самые 45 байт в секунду — это лучшее, что мы видели в космосе. На стенде на Земле бывало 50–55, абсолютный рекорд — 57.
Когда протокол начал более-менее гарантировать, что команда дошла до спутника, выполнилась и вывод снят обратно, только тогда стало возможно строить процесс. Завести пользователей, настроить тот самый вход, который снаружи выглядит как SSH, запустить скрипты инициализации, гонять пачки передачи. Всё это должно было работать как библиотечные функции для веб-клиента, того, что дальше собирал Кирилл. Загрузка файлов тоже пакетирована, файл разбивается на чанки.
Login incorrect
А вот эта часть доставила нам невероятный геморрой. Кирилл делал мост между пользователем и бэкендом через сокеты. Задача звучит скромно: принять с фронта строчку текста, отдать назад ответ спутника, а перед этим автоматически пройти авторизацию на борту.
Проблема в том, что после перезагрузки Linux запрашивает логин. Перезагрузка случается часто, потому что между сеансами связи железка выключается контроллером. Пока спутник летит без нас, он заряжает аккумулятор. А протокол стабильной связи — внутри сервера, то есть он ещё не работает, надо всё повторять по 10 раз и надеяться, что дошло правильно.
Дальше скрипт ждёт, когда загрузится Linux и появится приглашение логина. Приглашения нет — отправляем Enter. Ждём. Опять нет — ещё Enter, примерно раз в три-пять секунд, по совету коллег. В хорошем случае приходит login:, мы пишем имя пользователя, борт в лучшем случае спрашивает пароль, мы вводим пароль и ждём входа.
Плохой случай выглядит так: мы ждём запрос пароля. Запрос не приходит. Мы отправляем Enter и получаем «Login incorrect». Оказывается, запрос пароля до нас просто не долетел, борт уже ждал пароль, а мы вместо пароля отправили ему пустую строку. Состояние сбрасывается, мы заново ждём логин. Так могло повторяться до трёх раз, после чего связь обрывалась, и подключение приходилось поднимать заново, а это тоже время.
Времени же на всё про всё — 10 минут. Бывало, что связь просто терялась. Из этих десяти логин занимает по-разному. Бывает три-четыре минуты. Бывает, что сессия заканчивается, а мы так и не залогинились. Начало сеанса — это когда спутник ещё на краю диаграммы направленности антенны, там шумно, и до запуска нашего протокола всё это чисто шансовая вещь. Эфир хороший — залогинились с первой попытки.
Если залогинились, первым делом запускаем на борту наш STP-скрипт (он на Python), протокол для обмена пакетами. В подтверждение того, что скрипт поднялся, отправляем строчку, условно «hello», и ждём «hello» в ответ. Бывает, что за сессию уходит тридцать-пятьдесят «hello», а ответа нет ни одного.
Зато, если всё поднялось, дальше красота. Отправляем строчку, получаем ответ. Если ответ большой, борт складывает его во временный файл, мы скачиваем файл частями, потом он удаляется. Никаких дублирований и потерь, ответ приходит целиком и по порядку. Ради этого всё и затевалось.
И да, длинные частые команды мы заменили на алиасы (они раскрываются обратно на стороне сервера), чтобы отправлять меньше данных.
Фото
Одно из самых очевидных использований спутника — фотосъёмка и потом спуск фотографий вниз.
Сначала нужно было отрегулировать размер, чтобы борт не пытался отправить нам 50 мегабайт. Полный файл при нашем канале качался бы неделю-две. С другой стороны, конечно, хотелось, чтобы на картинке хоть что-то было видно.
Картинка жмётся до 7-10 килобайт. То есть можно забрать за 3-5-7 минут, как раз в рамках сессии.
Вторая проблема — сам аппарат. Он крутится не вокруг своей оси, а вообще в какую угодно сторону. Камера болтается. Поэтому мы делаем не один снимок, а серию кадров с интервалом пять секунд. Как вы уже знаете, в работу берём только тот файл, в котором больше байт. Если чёрный космос, он хорошо сжимается и весит мало. Если в нём много байт, значит, там что-то есть, скорее всего, Земля. Правда, иногда все кадры всё равно сняты, когда камера смотрит от планеты. Тогда возвращается самый большой чёрный квадрат.
Скачивает всё это File Downloader, скрипт со стороны грида, который тянет файл по байтам в хранилище. Борт перед файлом шлёт специальную магическую строку-заголовок, по ней шлюз переключается в режим приёма и блокирует ввод команд, потому что параллельно в этот канал лучше ничего не совать.
Прикол с NORAD
Это ещё одна звёздочка к этой задаче. Мы про это на старте не думали вообще. Спутник маленький и летит быстро, поэтому антенну наземной станции нужно точно наводить. А для этого нужно знать, где он конкретно. Эти данные даёт NORAD, американо-канадское командование воздушно-космической обороны (американская ПВО), которое присваивает каждому объекту на орбите номер, отслеживает его траекторию и, по сути, делает всё за вас. Берёте TLE, набор орбитальных элементов, вбиваете в грид-станцию и наводитесь.
Данные общедоступные и бесплатные, с ограничениями, наверное, для тех, кому нужна особая точность, но нам хватало. Так было всё время. А примерно в июле оказалось, что американцы стали обновлять данные по нашим аппаратам гораздо реже. Раз в пять дней. В первый день после обновления связь хорошая. К четвёртому-пятому спутник улетает совсем в другое место, отклонения накапливаются, и антенна ищет его там, где написано, а там его чаще всего нет.
Ну и пришлось искать, как рассчитывать TLE самим: экстраполировать, подбирать баллистические модели, с полным геморроем. Видимо, военное время, американцы решили не делиться. Почему именно так и почему сейчас — непонятно. ОКБ написали американским военным. Те вежливо ответили: «Спасибо за ваш запрос, мы займёмся вашей проблемой». И, естественно, ничем не занялись. Тогда ОКБ срочно подключили МГУ, где есть подразделение, которое занимается космическими исследованиями и у которого своя методика движения небесных тел. Привет мехмату, кстати! По их модели стали вручную пересчитывать и корректировать те данные, что есть в TLE. Сделали несколько пробных замеров, вроде получается.
Считать орбиту самим — вообще нормальная практика у кубсатчиков. AMSAT прямо пишет: после групповых запусков первые пять-десять дней уходят на то, чтобы понять, какой объект чей. SatNOGS и вовсе восстанавливает элементы по доплеровскому сдвигу.
На середину августа возраст последнего TLE у того аппарата, с которым мы работаем сейчас, — трое суток, у соседа по запуску — пять. Для объекта на 300 километрах норма — несколько обновлений в сутки, потому что атмосфера меняет орбиту каждый день.
Объявлений о том, что данные по российским объектам ограничили, мы не нашли нигде. Единственный похожий прецедент — 2022–2023 годы, когда Space-Track почти год отдавал всем «ограниченные обновления TLE» и не объяснял, почему.
Что со спутником сейчас
Когда с NORAD более-менее разобрались и начали тестироваться, оказалось, что спутники уже стремительно сходят с орбиты и жить им осталось недели две.
Мы сидим и такие: зашибись. Начали тестировать в темпе, в ходе тестирования — ещё какие-то мелкие ошибки. И вот день запуска, всё готово, а спутник не отвечает. Собственно, ровно в день, когда мы хотели давать публичный доступ, он и ударился о небесную твердь.
Последнее, что мы знаем про основной аппарат, — орбита 270 километров несколько дней назад, а потом ушёл со связи. За неделю назад на связь не вышел, что неудивительно. Вообще, ниже 300 — уже очень рискованная зона. Формально спутники сгорают на 150 километрах, но это про большие аппараты, а у нас пикоспутник без дополнительных слоёв защиты: как только атмосфера становится плотнее, он, скорее всего, просто ломается. На тех высотах, где он сейчас, никакая радиосвязь невозможна.
Дублёров у нас было два, с той же прошивкой. За счёт этого появился второй шанс: мы накатили на дублёр всё необходимое (прошивка одинаковая, но обновление всё равно потребовалось) и переключились. Дублёр сейчас на 330 километрах, выше, чем был наш, и выстрелен он был на неделю позже, так что у него, по идее, на неделю больше. По пессимистичному сценарию — до конца этой недели полетает и будет принимать наш сигнал, на следующей, скорее всего, повторит судьбу первого. Может, полетает до конца августа. А может, завтра выключится.
Дублёру не хватает заряда на полноценный цикл, поэтому не всегда окно нормально открывается: может так быть, что сеанс связи есть, а заряд ещё не накопился, тогда оборудование не включается.
На момент написания доступ открыт, и люди подключаются. С горем пополам, но подключаются. Хотели мы, конечно, совсем не так, но каким-то образом работает. Вход через аккаунт RUVDS, дальше слоты бронируются через Telegram-бота, который ходит в API грида за свободными окнами и бронирует их. Сеансов несколько в день, за полчаса до сеанса нужно подтвердиться; если не подтвердил, сеанс отменяется. Многие на этом теряют свои сеансы.
Воспользоваться этим успеют, думаю, несколько десятков человек. Пара десятков уже поработала, за неделю, может, наберётся ещё пара десятков. Денег мы за это не берём, это не услуга: как получилось, так получилось.
После каждого пользователя всё удаляется, файлы не накапливаются: это максимально близко к использованию виртуалки, насколько вообще возможно при таком канале. От модели «спутник что-то постоянно вещает, а радиолюбители слушают» мы отказались сознательно. Интерес был именно в том, чтобы работать с аппаратом как с сервером.
Когда аппарат замолчит, скажите, надо ли открыть на пару недель по той же схеме доступ к наземному образцу, тому самому, что стоит на вибростенде в лаборатории. Поиграться можно будет, но фотографировать он будет стену.
Раз десять-пятнадцать за этот год мы были близки к тому, чтобы просто плюнуть
Не плюнули благодаря людям, которые собрались и каждый дотащил свою часть. Каждый сделал огромную работу, всем спасибо. Ценность второго эксперимента, на мой взгляд, гораздо выше.
Вторая боль всех участников — гонка со временем. Запуск на полгода раньше, год жизни аппарата стал 8 месяцами. ОКБ уже собирает заявки на следующий TriSat, на 2027 год. Не знаю, будет ли следующий спутник, это всё было очень тяжело. Но пока есть нынешний — можете его затестить. Инструкция выше.
Если мы решим запускать ещё что-то (а я в этом очень сильно сомневаюсь), на той же платформе будет гораздо проще: что-то уже есть, протокол написан, грабли пересчитаны. Но прямо сейчас — не хочу.
Автор: ntsaplin

