KerboScript в примерах и задачах. Часть 2.5. Триггеры. Завершение спутниковой сети

Kerbal Space Program » Гайды

Часть 0. Базовые функции
Часть 1. Выход на орбиту Кербина
Часть 2. Гомановский переход и запуск первого ретранслятора
В предыдущей части обещалась сеть из четырёх спутников, из которых был запущен один. Здесь описан запуск остальных. В качестве знакомства с новой функцией KerboScript рассмотрим триггеры.
Запуск оставшихся трёх спутников, по сути, повторяет запуск первого с единственным нюансом: на целевую орбиту они должны выходить не абы куда, а со сдвигом на 90, 180 и 270 градусов относительно первого спутника. Это требует точного расчёта времени прожига для создания переходной орбиты. Расчёт, впрочем, не слишком сложен.
Переход между орбитами

Рисунок 1. К расчёту выхода на переходную орбиту. Положительное направление отсчёта углов показано зелёным, отрицательное - красным.

Смотрим рисунок 1. Сумма угла φ1 между радиус-векторами аппарата и цели из центра Кербина в апоцентре и угла поворота цели φt за время трансфера за вычетом угла между аппаратом и целью в перицентре φ0 (углы считаются с учётом направления поворота, т.е. φ0 и φ1 на рисунке имеют разные знаки) равна 180o, или φ0 = φ1 + |Δφt| - 180o.
Рассчитав угол φ0 по желаемому φ1, рассчитываем время манёвра. Если наш аппарат и цель движутся по круговым орбитам на восток, то, принимая во внимание положительное направление углов в KSP по часовой стрелке,
φsat = φ0sat - t/Tsat×360o
φtgt = φ0tgt - t/Ttgt×360o
φsat-tgt = φtgt - φsat = φ' + (t/Tsat - t/Ttgt)×360o
где Tsat и Ttgt - периоды орбит аппарата и цели, соответственно.
В нашем случае необходимо по желаемому углу φsat-tgt в конце трансфера найти угол φ', под которым начинать манёвр. В качестве времени t берётся половина периода трансферной орбиты. Период вычиcляется по известной величине большой полуоси:
T = 2πA3/21/2
Большая полуось A переходной орбиты, очевидно, равна полусумме радиусов начальной и финальной орбит.
Используя эту математику, слегка переписываем функцию расчёта переходного манёвра. Теперь она будет брать в качестве аргументов желаемый объект и желаемый угол в апоцентре между направлениями на аппарат и на цель.

function transfernode {
  parameter tgt is Mun.
  parameter phitotgt is 0. // при запуске без аргументов выводим на траекторию столкновения с Муном
  
  // расчёт dV
  local newsma to tgt:orbit:semimajoraxis.
  local r0 to orbit:semimajoraxis.
  local v0 to velocity:orbit:mag.
  
  local a1 to (newsma + r0)/2. // большая полуось переходной орбиты
  local Vpe to sqrt( body:mu * ( 2/r0 - 1/a1 ) ).
  local deltav to Vpe - v0.
  
  // расчёт точки манёвра
  local t12 to constant:pi * a1^1.5 / body:mu^0.5. // время прохождения от перицентра до апоцентра
  local tomega to 360/tgt:orbit:period.
  local phitrans to phitotgt + tomega*t12 - 180. // под этим углом нужно начинать манёвр
  
  local phinow to 180 - vang( body:position, tgt:position - body:position ).
  if vcrs( body:position, tgt:position ):y > 0 { set phinow to 360 - phinow. }
  local omegaeff to 360/orbit:period - tomega.  // угловая скорость, с которой мы движемся относительно цели
  local etatrans to ( phitrans - phinow ) / omegaeff. // когда должен быть манёвр
  // если нет времени на манёвр, он переносится на следующий период
  if etatrans < (deltav * mass / ship:availablethrust + 30) { set etatrans to etatrans + 360/abs(omegaeff). }
  
  print "Transfer burn: " + round(v0) + " -> " + round(Vpe) + "m/s".
  set nd to node(time:seconds + etatrans, 0, 0, deltav).
  add nd.
}

Для расчёта времени до манёвра вычисляется начальный угол φ' до цели. Для получения знака используется векторное произведение. С учётом того, что аппарат находится в начале координат, то для того, чтобы угол φ' был положителен, нужно условие сонаправленности [-RKerbin × (Rtgt - RKerbin)] с осью Y (если обе орбиты экваториальны). Свойство векторного произведения [RKerbin × RKerbin] = 0 позволяет немного сократить условие положительности угла до [RKerbin × Rtgt]y < 0, что и сделано в скрипте. Расчёт потребной дельты аналогичен тому, что было для запуска первого спутника.
Теперь с этой новой функцией расчёта переходной орбиты можно запустить три новых крафта, аналогичных KommSat из первой части, указав им выходить на одну орбиту с первым спутником с разницей фаз 90o, 90o и 270o - и сеть будет завершена. Но я в рамках гайда предлагаю пойти немного другим путём и запустить все три спутника на одном носителе в проекте Konstellation. Именно ради унификации с этим проектом в прошлой части программа работы носителя и не включала довывод с суборбитальной траектории силами спутника - при выводе трёх сразу доразгон силами лишь одного мне кажется нечестным.
Кто купил КоммСатов пачку...

Рисунок 2. Носитель на три спутника. Аэродинамика, бессердечная ты сволочь, центр масс заставляешь бустерами поднимать. 

Добавление к носителю от KommSat баков пообъёмнее и бустеров помощнее вполне позволяет без изменения скрипта вывести три спутника разом, оставив последние 20 м/с на сход с орбиты. А вот логику скрипта для спутников нужно несколько доработать.
Теперь у нас есть три спутника, для каждого из которых нужно сделать свой загрузочный скрипт. Рабочий скрипт желательно максимально унифицировать. Программа работы предлагается следующая:
  • Режим 1
    Ждать отделения. Для первого спутника сигнал передаёт ракета, для остальных - предыдущий спутник.
  • Режим 2
    По условному сигналу отстрелиться от пачки. Не забыть перед этим передать сигнал следующему, что дальше его очередь.
  • Режимы 3-8
    Аналогично предыдущей части гайда - выйти на целевую орбиту, только теперь с более конкретно заданными параметрами.
  • Режим 9
    Аналогично предыдущей части гайда - сидеть на целевой орбите, сориентировавшись на Солнце.

Поскольку режимы 1 и 3-9 полностью аналогичны тому, что в предыдущем гайде, нужно переписать только режим 2.
Как организовать отделение по условному сигналу? Рассмотрим такой вариант, как использование триггера. Триггер - это специальный тип условной конструкции, который организует постоянную фоновую проверку некоторого условия. При выполнении условия выполняется прерывание и выполняется код триггера. Пример:

if ship:availablethrust = 0 { stage. } // запустит следующую ступень, если в момент вызова нет доступной тяги
when ship:availablethrust = 0 then { stage. } // на каждом цикле обсчёта физики проверяет, есть ли доступная тяга
// как только тяги не станет, запускает следующую ступень

Триггеры есть двух типов: when ... then и on. Первый проверяет выполнение условия, указанного после when, второй - выполняет код, если выражение после on поменяло значение. Примеры использования:

when altitude > body:atm:height then print "We have escaped atmosphere".
lock steering to up.
stage.
wait until false.
// если в ступени достаточно топлива, чтобы при вертикальном подъёме выбросить за границу атмосферы,
// триггер сработает при пересечении границы атмосферы и выведет в консоль сообщение
// даже несмотря на вечный wait
on body:radius print "SOI has changed".
// триггер сработает, когда поменялось название центрального тела

На основе триггера on можно писать очень сложные последовательности для групп механизации, чем, по существу, мы сейчас и займёмся.
Итак, в скрипт спутника KommSat вносим следующие изменения:

function satprogram {
  if satmode = 0 {
    print "Waiting for separation.".
    wait until exists("released.txt").
    print "Separation confirmed. ".
    wait 20.
    nextmode().
  }
  if satmode = 1 {
    print "Aligning for optimal solar panel performance.".
    AlignToSun().
    wait 15.
    on rcs { nextmode(). }
  }
  until satmode > 1 {
    wait 1.
  }
  if satmode = 2 {
    confirmsep(nextsat).
    decouple(nextdecoupler).
    log "set nextsat to " + char(34) + "NULL" + char(34) + "." to "mode.ks".
    log "set nextdecoupler to " + char(34) + "NULL" + char(34) + "." to "mode.ks".
    // char(34) - это кавычка, другим способом её не залогировать
    list engines in el.
    for e in el { if not e:ignition e:activate. } // stage может не работать, если отделённый спутник окажется неактивным
    on rcs { nextmode(). }
  }
  until satmode > 2 {
    wait 1.
  }
  if satmode = 3 {
    log "set target to vessel(" + char(34) + target:name + char(34) + ")." to "mode.ks".
    transfernode(target, 90).
    nextmode().
  }
  if satmode = 4 {
    exenode().
    nextmode().
  }
  if satmode = 5 {
    wait 10.
    aponode(apoapsis).
    nextmode().
  }
  if satmode = 6 {
    exenode().
    nextmode().
  }
  if satmode = 7 {
    TrimPeriod(target:orbit:period).
    nextmode().
  }
  if satmode = 8 {
    set dish to ship:partsnamed("HighGainAntenna5")[0].
    set d to dish:getmodule("ModuleRTAntenna").
    d:doevent("activate").
    d:setfield("target", Mun).
    print "Satellite deployed to operational orbit.".
    nextmode().
  }  
  until false AlignToSun().
}

Здесь введено два триггера, которые срабатывают при изменении статуса RCS (т.е. когда мы нажимаем R на клавиатуре). Первый начинает работу в режиме 1 - после того, как спутник отделился от отработавшей свою программу части ракеты. При срабатывании этого триггера спутник переходит в режим 2 - отделяется от последующих и инициализирует следующий триггер. Далее нужно выбрать цель и снова нажать R. По второму триггеру запускается программа перехода на рабочую орбиту на 90o впереди перед целью.
Здесь нужно отметить вот что. Движок игры не позволяет всем спутникам сразу проводить манёвры в разных частях орбиты, т.е. выход на рабочую орбиту они должны производить по очереди. Это значит, что когда активный аппарат уходит далеко от остальной части, программа на них останавливается, а при переключении на них происходит перезагрузка. Перезагрузка происходит с нуля, т.е. предыдущее состояние забывается, в том числе и триггеры. В прошлой части объяснено, как во вспомогательный файл записывать режим работы, на котором программа остановилась, и потом восстановить его. Здесь же дополнительной задачей стоит то, что триггеры в коде нужно расставить так, чтобы при перезагрузке они правильно реинициализировались. Приведу пример:

runpath("mode.ks").
function nextmode {
  parameter newmode is satmode+1.
  set satmode to newmode.
  log "set satmode to " + newmode + "." to "mode.ks".
  AlignToSun().
}
// Текущий код
  if satmode = 1 {
    print "Aligning for optimal solar panel performance.".
    AlignToSun().
    wait 15.
    on rcs { nextmode(). }
  }
  until satmode > 1 {
    wait 1.
  }
  if satmode = 2 ...
// Нерабочий код
  if satmode = 1 {
    print "Aligning for optimal solar panel performance.".
    AlignToSun().
    wait 15.
    on rcs { nextmode(). }
    nextmode().
  }
  until satmode > 2 {
    wait 1.
  }
  if satmode = 3 ...

И работающий код, и нерабочий сделают одно и то же, если аппарат в промежутке ожидания триггера не будет перезагружен. Однако в случае перезагрузки они работают по-разному. В рабочем коде после перезагрузки аппарат переходит в satmode 1 и перезапускает триггер. В нерабочем же после перезагрузки аппарат оказывается в satmode 2, триггер заново не создаётся, и ожидание длится вечно.
Для обеспечения корректности работы после перезагрузки предназначено и странное логирование в satmode 2 - после того, как аппарат уже отделился, по штатным значениям ни nextsat, ни nextdecoupler найдены не будут.
Теперь поймём, что делают необъявленные пока функции confirmsep и decouple. Первая передаёт оставшейся связке, что спутник отделился, а вторая отстреливает его.
function decouple {
  parameter dcname.
  if dcname <> "NULL" {
    ship:partstagged(dcname)[0]:getmodule("ModuleDecouple"):doevent("Decouple").
  }
}
function confirmsep {
  parameter nextdrive.
  if nextdrive <> "NULL" {
    local path to nextdrive + ":/released.txt".
    create(path).
  }
}

Функция decouple сделана потому, что при разделении крафта на несколько порядок срабатывания ступеней может измениться непредсказуемым образом. Чтобы избежать срабатывания неправильного разделителя, в редакторе щёлкаем по ним ПКМ и выбираем "Change Name Tag" (в карьере доступно только после прокачки ЦВС до 2 уровня; впрочем, в крафте больше 30 частей, так что без ЦВС 2 уровня его всё равно не запустить), где называем каждый разделитель индивидуальным именем. Заодно ставим Disable Staging, чтобы исключить случайное срабатывание.
Функция confirmsep просто пишет на следующий спутник файл, который сигнализирует тому, что его очередь следующая. Проверка на NULL вводится потому, что последнему оставшемуся не надо ни отделяться, ни кому-то что-то писать.
Таким образом, основные функции для работы всех спутников одинаковы. Различаются только начальные параметры, которые записываются в boot-файлы. Пример для верхнего спутника (он первый, поскольку с него начиналась сборка):

set volume(1):name to "sat1".
if status="PRELAUNCH" { 
  copypath("0:/Konstellation.ks","satprogram.ks").
  log "local satmode to 0." to "mode.ks".
}
local nextsat to "sat2".
local nextdecoupler to "Decoupler1".
runpath("mode.ks").
runpath("satprogram.ks").
satprogram().

Как можно видеть, первой строчкой мы задаём новое имя для volume(1), что позволяет в программах, выполняемых на других модулях, не угадывать номер данного раздела, а использовать текстовый алиас. Для второго и третьего спутников, естественно, алиасы будут sat2 и sat3.
Также, по сравнению с предыдущим гайдом, несколько оптимизирован профиль запуска и подправлена функция TrimPeriod. Скачать всё вместе с крафтом можно внизу под катом.
Итак, всё готово к запуску. Пускаем ракету Konstellation, дожидаемся выхода на опорную орбиту и схода носителя. Если сверху не пролетает первый запущенный KommSat, то нужно ещё дождаться восстановления связи с KSC. При наличии связи "включение" RCS отделит первый спутник. Затем выбираем для него цель и "выключаем" RCS (если запустили триггер, не выбрав цель - программа упадёт, но не беда, достаточно перезагрузиться, набрав в консоли reboot, и сделать уже всё как надо), после чего он выходит на орбиту на 90o впереди от цели. Переключаемся на то, что осталось на опорной орбите, повторяем RCS - указать цель - RCS. После выхода второго спутника повторяем процедуру с третьим. В итоге имеем красивую группировку спутников с синхронизованными до миллисекунды периодами. Сеть должна работать как для низкоорбитальных аппаратов, так и для программы "Мун".
KerboScript в примерах и задачах. Часть 2.5. Триггеры. Завершение спутниковой сети

Рисунок 3. Quadratisch. Praktisch. Gut.

В следующей части - как попасть в Мун с опорной орбиты и как сделать это прямым пуском.

Крафт (kOS, RemoteTech), скрипты:
KerboScript в примерах и задачах. Часть 4. Разбираемся в орбитах.
15 мая 2017 в 18:57, Гайды
KerboScript в примерах и задачах. Часть 3. Предсказание орбиты. Летим на Муну.
5 апр 2017 в 21:21, Гайды
  1. MrKerbMan

    MrKerbMan @Керб 28 марта 2017 21:16

    Вау. Вот ведь запарился человек. Давно таких годных гайдов не читал.

    1. Pand5461

      Pand5461 29 марта 2017 13:15 Автор

      Спасибо. Мне и самому польза есть - спагетти-код свой немного почистил.

  2. Airtra

    Airtra @Airtra 31 марта 2017 03:55

    Молодец! И спасибо за интересную информацию! Ждем продолжения беспилотных миссий...

  3. StanislavSay

    StanislavSay @Stanislav 31 марта 2017 08:27

    Хорошо изложено, жду продолжения)

  4. nefirma

    nefirma @Денис Филиппов 2 апреля 2017 14:52

    Спасибо большое за цикл статей! Все хорошо разложено и объяснено, мне было полезно ;-)

    Жду продолжения!

    З.Ы. Специально ради "+" автору наконец-то зарегистрировался на этом сайте ;-)

{login}
  • bowtiesmilelaughingblushsmileyrelaxedsmirk
    heart_eyeskissing_heartkissing_closed_eyesflushedrelievedsatisfiedgrin
    winkstuck_out_tongue_winking_eyestuck_out_tongue_closed_eyesgrinningkissingstuck_out_tonguesleeping
    worriedfrowninganguishedopen_mouthgrimacingconfusedhushed
    expressionlessunamusedsweat_smilesweatdisappointed_relievedwearypensive
    disappointedconfoundedfearfulcold_sweatperseverecrysob
    joyastonishedscreamtired_faceangryragetriumph
    sleepyyummasksunglassesdizzy_faceimpsmiling_imp
    neutral_faceno_mouthinnocent
Последние сообщения с форума
  • Автор
    Тема в разделе: Технические вопросы
    Просмотров: 24259
    Ответов: 68
  • Автор
    Тема в разделе: Моды
    Просмотров: 1460
    Ответов: 2
  • Автор
    Тема в разделе: В ангаре у Боба
    Просмотров: 206227
    Ответов: 1484
  • Автор
    Тема в разделе: Игровой процесс
    Просмотров: 1646
    Ответов: 1
  • Автор
    Тема в разделе: Модераторский раздел
    Просмотров: 7608
    Ответов: 21
    Все сообщения..
    Полный список последних сообщений
    Loading...

    Нашли ошибку?
    Вы можете сообщить об этом администрации.
    Выделив текст нажмите Ctrl+Alt