Часть 0 и
часть 1 гайда должны были научить читателя писать простой автопилот для вывода спутника на орбиту. В этой части используем предыдущие наработки и некоторые сведения из орбитальной механики для запуска первого спутника будущей ретрансляционной сети.
Для низко- или среднеорбитальных группировок идеально использование ненаправленных антенн в сети, поскольку они соединяются с любым аппаратом, который находится в зоне действия. Из начальных антенн Communotron 16 имеет дальность действия 2500 км, что достаточно для сети из четырёх спутников, расположенных по углам квадрата на круговой орбите высотой 2500/√2 км -
RKerbin = 1167 км. В пределах этой высоты можно построить "третьсинхронную" орбиту с периодом 2 часа на высоте 1067 км. Спутник на такой орбите будет проходить над каждой точкой экватора дважды в сутки в одно и то же время.
Конструкция спутника
Технические подробности:
Для спутника нужны:
- модуль kOS
- беспилотный командный модуль
- антенна Communotron 16
- батареи
- солнечные панели
- двигательная установка
Чтоб два раза не ходить, прицепим ещё остронаправленную антенну HG-5, которая имеет дальность 20 тыс. км, а значит, добивает до Муна, что позволит начать программу исследования Муна без запуска дополнительных ретрансляторов.
Количество батарей рассчитываем, исходя из темнового времени орбиты (
калькулятор) и потребления аппарата. Темновое время составит около 840 секунд, а потребление должно укладываться в 1 EC/s со включенными антеннами, так что пяти Z-200 должно быть достаточно. Из похожих соображений, четырёх OX-STAT должно хватать с избытком, если мы сумеем ориентировать аппарат батареями к солнцу.
Переход с 80х80 км на 1067х1067 км требует около 790 м/с, что с избытком обеспечивается одним баком Oscar-B и "Муравьём".
Для запуска используем ту же ракету, что и для KSPutnik'а, добавив топливных баков.
Рисунок 1. Низкоорбитальный коммуникационный спутник.Кроме вывода спутника на целевую орбиту, хотелось бы также организовать пуск без ненужного засорения космоса. Вариантов два: либо изначально вывести на траекторию с перицентром внутри атмосферы и потом слегка довывести спутником, либо после отделения спутника затормозить последнюю ступень и сбросить её в атмосферу. По причинам, которые будут ясны позднее, предлагаю реализовать второй вариант. Для этого
хватит пары старых добрых сепратронов на носитель поставим свой командный модуль, модуль kOS и гиродины.
Памятуя об избыточных световых и плазменных эффектах в предыдущей части, добавим в скрипт вывода дросселирование двигателя при достижении тяговооружённости выше определённого уровня.
function CapTWR {
parameter maxTWR is 3.0.
local g0 to Kerbin:mu/Kerbin:radius^2. // TWR считается относительно веса на уровне моря
lock throttle to min(1, ship:mass*g0*maxTWR / max( ship:availablethrust, 0.001 ) ). // без max() будет деление на ноль при пропадании тяги
}
Функция схода с орбиты:
function DumpRocket {
lock steering to ship:retrograde.
wait until vang(facing:vector, retrograde:vector) < 5.
lock throttle to 1.
wait until periapsis < 10000.
lock throttle to 0.
}
Теперь можем написать полностью скрипт для ракеты, который выводит спутник на опорную орбиту и роняет взлётную ступень обратно.
function VertAscent {
lock steering to heading(90,90).
}
function CapTWR {
parameter maxTWR is 3.0.
local g0 to Kerbin:mu/Kerbin:radius^2. // TWR считается относительно веса на уровне моря
lock throttle to min(1, ship:mass*g0*maxTWR / max( ship:availablethrust, 0.001 ) ). // без max() будет деление на ноль при пропадании тяги
}
function GravityTurn {
parameter vstart. // скорость, при которой начинается разворот
parameter AP45 is apoapsis. // апоцентр при тангаже 45 градусов
parameter APstop is 60000. // апоцентр, по достижении которого ракета ложится горизонтально
parameter v45 is 500. // скорость, при которой угол тангажа должен быть 45 градусов
local vsm to velocity:surface:mag. // величина скорости относительно поверхности
local pitch to 0.
if ( vsm < v45 ) {
set pitch to 90 - arctan( (vsm - vstart)/(v45 - vstart) ). // линейно менять тангаж от скорости оказалось плохо, по арктангенсу довольно неплохо получается
}
else {
set pitch to max(0, 45*(apoapsis - APstop) / (AP45 - APstop) ). // линейно меняем тангаж, на APstop укладываем ракету горизонтально
}
lock steering to heading( 90, pitch ).
// возложим на kOS функции Kerbal Engineer
print "Apoapsis: " + round( apoapsis/1000, 2 ) + " km " at (0,30).
print "Periapsis: " + round( periapsis/1000, 2 ) + " km " at (0,31).
print " Altitude: " + round( altitude/1000, 2 ) + " km " at (24,30).
print " Pitch: " + round( pitch ) + " deg " at (24,31).
}
function circularize {
local th to 0. // в этой переменной будет необходимый уровень тяги
local Vcircdir to vxcl( up:vector, velocity:orbit ):normalized. // направление круговой скорости такое же, как у горизонтальной компоненты орбитальной скорости
local Vcircmag to sqrt(body:mu / body:position:mag). // mu - это гравитационный параметр планеты, произведение массы на гравитационную постоянную
local Vcirc to Vcircmag*Vcircdir.
local deltav to Vcirc - velocity:orbit.
// начинаем прожиг, поворачивая ракету постоянно в сторону маневра
lock steering to lookdirup( deltav, up:vector).
wait until vang( facing:vector, deltav ) < 1. // убеждаемся, что прожиг начинается в нужной ориентации
lock throttle to th.
until deltav:mag < 0.05 {
set Vcircdir to vxcl( up:vector, velocity:orbit ):normalized.
set Vcircmag to sqrt(body:mu / body:position:mag).
set Vcirc to Vcircmag*Vcircdir.
set deltav to Vcirc - velocity:orbit.
if vang( facing:vector, deltav ) > 5 {
set th to 0. // если сильно не туда смотрим, надо глушить двигатель
}
else {
set th to min( 1, deltav:mag * ship:mass / ship:availablethrust ). // снижаем тягу, если приращение скорости нужно небольшое
}
wait 0.1.
}
set th to 0.
set ship:control:pilotmainthrottle to 0.
unlock throttle.
}
function startnextstage {
// если есть живые двигатели, то делать ничего не будет;
// в противном случае будет запускать ступени, пока не появится тяга
until ship:availablethrust > 0 {
wait 0.5.
stage.
}
}
function DumpRocket {
lock steering to ship:retrograde.
wait until vang(facing:vector, retrograde:vector) < 5.
lock throttle to 1.
wait until apoapsis < 10000.
lock throttle to 0.
}
function gettoorbit {
parameter Horb to body:atm:height + 10000.
parameter GTstart to 1000. // высота начала разворота
parameter GTendAP to 60000. // заканчиваем разворот, когда апоцентр на этой высоте
local maxTWR to 2.5.
// запомним, как ракета стоит на столе, в этом положении взлетаем
lock throttle to 1.
local initialpos to ship:facing.
lock steering to initialpos.
startnextstage().
until altitude > GTstart {
VertAscent().
startnextstage().
CapTWR(maxTWR).
wait 0.01.
}
// запомним параметры для функции гравиразворота:
local GTStartSpd to velocity:surface:mag. // при какой скорости начали разворот
local Apo45 to apoapsis. // какой апоцентр был при тангаже 45 градусов
local lock pitch to 90 - vang( up:vector, velocity:surface ). // переменная с тем же именем, что и в другой функции
// т.к. объявлена локально, конфликта имён возникать не должно
// строим апоцентр
until apoapsis >= Horb {
if pitch >= 45 { set Apo45 to apoapsis. } // перестанет обновляться после тангажа 45 градусов - то, что требуется
GravityTurn(GTStartSpd,Apo45,GTendAP).
startnextstage().
CapTWR(maxTWR).
wait 0.01.
}
lock throttle to 0.
// ждём, выхода из атмосферы, после чего сбрасываем ненужный более обтекатель
lock steering to prograde.
wait until altitude > body:atm:height.
print "We are in space. Deploying payload fairing. ".
stage.
//вблизи апоцентра скругляем
wait until altitude > apoapsis - 200.
circularize().
print "We are in orbit: " + round(apoapsis/1000,2) + "x" + round(periapsis/1000,2) + " km. ".
wait 5.
print "Deploying antennae.".
set alist to ship:partsnamed("longAntenna").
for an in alist {
set d to an:getmodule("ModuleRTAntenna").
d:doevent("activate").
}
// чтобы при торможении не попасть выхлопом в спутник, ракета разворачивается перпендикулярно
lock steering to up.
wait 10.
print "Releasing payload.".
stage.
wait 10.
DumpRocket().
set ship:control:pilotmainthrottle to 0.
set ship:control:neutralize to True. // отпустить управление
}
gettoorbit().
Чтобы вручную не писать каждый раз в терминале команды на копирование и запуск файла, поместим их в загрузочный скрипт. Загрузочный скрипт - это та программа, которая запускается при загрузке крафта в память - это может происходить как при старте, так и при переключении на крафт, находящийся вне Physics Range. Скрипты для начальной загрузки можно выбирать только из тех, которые лежат в папке
Ships/Script/boot. Выбрать необходимый скрипт можно в редакторе по ПКМ на kOS модуль.
ВАЖНО: у каждого модуля в крафте загрузочный скрипт задаётся отдельно. Дальше мы это используем для задания работы спутника после отделения от носителя.
Создаём файл
Ships/Script/boot/KommsatLVboot.ks следующего содержания:
copypath("0:/KommsatLV.ks","launch.ks"). // здесь должен быть путь к скрипту для ракеты
// 0:/ - это папка Ships/Script/
runpath("launch.ks").
В kOS модуле на носителе выбираем
KommsatLVboot.ks в качестве загрузочного скрипта. Теперь программа ракеты запускается при старте автоматически.
Для вывода спутника на целевую орбиту нужно, кроме задачи расчёта и выполнения гомановского перехода, как-то передать на него управление после разделения с носителем. С одного kOS модуля на другой переключиться нельзя, командой switch только можно переключить путь по умолчанию, откуда модуль считывает файлы. Можно ввести команды "с земли" через терминал, но траектория вывода такова, что к моменту разделения спутник уже за горизонтом, и нужно целую орбиту ждать его прохода над ЦУПом. Поэтому сделаем так, чтобы спутник мог сам понять, что он уже выведен на орбиту. Есть (вероятно) много способов это сделать, но я предпочитаю со спутника проверять, не появился ли в файловой системе определённый файл, а с носителя его создавать перед отделением.
Итак, начинаем писать файл
Ships/Script/Kommsat.ks
wait until exists("released.txt"). // ожидаем появления файла
wait 20. // даём ступени сойти с орбиты
satprogram(). // а вот это надо ещё написать
В программе ракеты, естественно, разделение нужно реализовать в виде
lock steering to up.
wait 10.
print "Releasing payload.".
create("2:/released.txt"). // kOS модуль спутника для ракеты имеет номер 2, а для спутника номер 1
stage.
Теперь переходим к программе действий спутника после отделения. Перво-наперво, ориентироваться солнечными панелями на свет. Смотрим ещё раз рисунок 1. Командный модуль при сборке в редакторе я не поворачивал, поэтому ориентирован он стандартным образом - верх на юг, нос вверх, правая сторона на запад. Таким образом, панели у него слева и справа. Значит, если повернуть спутник верхней стороной на солнце, а потом на 90
o по крену в любую сторону, то панели будут смотреть на солнце. Удобно то, что солнце в экваториальной плоскости, поэтому направление на него с орбиты Кербина всегда (почти) перпендикулярно направлению на север (или юг).
function AlignToSun {
lock steering to lookdirup(north:vector,Sun:position) + R(0,0,90).
}
Теперь будем организовывать трансфер.
- Вычисляем высоту целевой орбиты, опираясь на желаемый период обращения 2 часа.
Используем очевидную формулу T = 2πR/V, где R = H + RKerbin - радиус орбиты, круговая скорость V рассчитывается по формуле V = (μ/R)1/2. Итого получаем T = 2πR3/2/μ1/2, или
μ - гравитационный параметр центрального тела, равный произведению его массы на универсальную гравитационную постоянную
- Вычисляем скорость в перицентре (более подробный гайд на эту тему).
Как известно, полная механическая энергия тела на орбите определяется величиной большой полуоси и равна ε = -μ/2A (на единицу массы). Из закона сохранения энергии получаем, что -μ/2A = -μ/r + v2/2, или
Большая полуось переходной орбиты будет равна, естественно, A = RKerbin + (Hnow + Htarget)/2, а расстояние от центра Кербина в её перицентре будет r = Hnow + RKerbin.
- Делаем манёвр перицентра в прогрейд с ΔV = VPe - Vnow.
- Аналогичным образом рассчитываем и выполняем манёвр апоцентра.
Манёвры перицентра и апоцентра будем проводить через узлы маневрирования. Из kOS можно получить узел как структуру, имеющую суффиксы
:eta // оставшееся до манёвра время
:deltav // вектор оставшегося изменения скорости
:prograde // dV в направлении прогрейда
:radialout // dV в радиальном направлении
:normal // dV в нормальном направлении
:orbit // предсказываемая орбита после выполнения манёвра
Поля
:eta,
:prograde,
:radialout и
:normal можно как получить, так и изменить, остальные можно только получить. Узел создаётся командой
node(utime, radial, normal, prograde)
где
utime - это время, на которое ставится узел.
Чтобы получить орбиту после манёвра, нужно добавить его в план полёта. Это делается командой
add. Удалить узел можно командой
remove.
set nd to node(time:seconds + 120, 0, 0, 10). // 10 м/с в прогрейд через 2 минуты
add nd. // добавили узел на траекторию
print nd:orbit:apoapsis. // печать апоцентра после выполнения
Напишем универсальную функцию для выполнения произвольного узла манёвра. Логика её работы:
- Оценить время выполнения манёвра.
Грубая оценка, использующая начальную массу аппарата и максимальный уровень тяги - это Δt = ΔV·m0/Fmax.
Более аккуратная оценка использует формулу Циолковского:
из которой нехитрыми преобразованиями получаем окончательную формулу
в которой f - это скорость сжигания топлива в кг/с, Isp - эффективный удельный импульс, который в случае нескольких активных двигателей равен отношению суммарной тяги к суммарной скорости потребления топлива.
Выполнять манёвр будем так же, как и циркуляризацию в прошлой части - привязываем управление к вектору ΔV и снижаем тягу в конце, когда манёвр почти выполнен.
function warpfor {
// вспомогательная функция, проматывает время на варпе
// warp:xTime (0:1) (1:5) (2:10) (3:50) (4:100) (5:1000) (6:10000) (7:100000)
parameter dt.
set t1 to time:seconds + dt.
if dt < 0 {
print "WARNING: wait time " + round(dt) + " is in the past.".
}
until time:seconds >= t1 {
set warp to round( log10( 0.356*(t1 - time:seconds) ) * 2 ).
wait 0.01.
}
}
function exenode {
local nd to nextnode. //nextnode возвращает ближайший узел манёвра
local done to False.
set ship:control:pilotmainthrottle to 0.
// оцениваем время манёвра
local g0 to Kerbin:mu/Kerbin:radius^2.
list engines in el.
local ispeff to 0.
local ff to 0. // fuel flow - потребление топлива
local tt to 0. // total thrust - тяга
for e in el {
set ff to ff + e:availablethrust/max(e:isp,0.01)/g0.
set tt to tt + e:availablethrust.
}
set ispeff to tt/ff.
if tt = 0 {
print "ERROR: No active engines!".
set ship:control:pilotmainthrottle to 0.
return.
}
set dob to mass/ff*(1 - constant:e^(-nd:deltav:mag/ispeff)).
print "Burn duration: " + round(dob) + " s, Max acc: " + round(ship:availablethrust/(mass - dob*ff),1) + " m/s^2 ".
warpfor(nd:eta-dob/2-60).
sas off.
print "Turning ship to burn direction.".
local np to lookdirup(nd:deltav,up:vector).
lock steering to np.
wait until vang( np:vector,facing:vector ) < 0.05 and ship:angularvel:mag < 0.05.
warpfor(nd:eta-dob/2-7).
print "Burn start " + round(dob/2) + "s before node.".
lock throttle to 0.
lock steering to lookdirup(nd:deltav,up:vector).
wait until nd:eta <= dob/2.
local dv0 to nd:deltav. // запомнили начальное направление манёвра
until done {
lock throttle to min( nd:deltav:mag * mass / ship:availablethrust, 1).
// прерывание, если выполнили лишнего
if vdot(dv0, nd:deltav) < 0 {
print "End burn, remain dv " + round(nd:deltav:mag,1) + "m/s, vdot: " + round(vdot(dv0, nd:deltav),1).
lock throttle to 0.
break.
}
if nd:deltav:mag < 0.1 {
print "Finalizing, remain dv " + round(nd:deltav:mag,1) + "m/s ".
wait until vdot(dv0, nd:deltav) < 0.01.
lock throttle to 0.
print "End burn, remain dv " + round(nd:deltav:mag,1) + "m/s ".
set done to True.
}
}
unlock steering.
set ship:control:pilotmainthrottle to 0.
wait 1.
remove nd.
unlock throttle.
}
Расчёт манёвра вывода на переходную орбиту по периоду конечной круговой орбиты:
function transfernode {
parameter targetperiod is 3600.
local aponew to ( targetperiod / 2 / constant:pi * sqrt( body:mu ) )^(2.0/3.0) - body:radius.
local r0 to body:radius + (apoapsis + periapsis)/2.
local v0 to velocity:orbit:mag.
// рассчитываем манёвр апоцентра
local a1 to (aponew + body:radius + r0)/2. // большая полуось
local Vpe to sqrt( 2 * body:mu * (1/r0 - 1/a1) ).
set deltav to Vpe - v0.
print "Transfer burn: " + round(v0) + ", dv:" + round(deltav) + " -> " + round(Vpe) + "m/s".
set nd to node(time:seconds + 180, 0, 0, deltav). // три минуты должно хватить на выполнение
add nd.
}
Для скругления орбиты после перехода напишем универсальную функцию для манёвра в апоцентре с изменением противоположной апсиды:
function aponode {
parameter newapsis is apoapsis.
print "Maneuver at apoapsis. Changing orbit: ".
print round(periapsis/1000,1) + "x" + round(apoapsis/1000,1) + " km -> " + round( min(apoapsis,newapsis)/1000,1) + "x" + round( max(apoapsis,newapsis)/1000,1) + " km ".
local Vnow to velocity:orbit:mag.
local Rnow to body:radius + altitude.
local Ra to body:radius + apoapsis.
local Va to sqrt( Vnow^2 + 2*body:mu*(1/Ra - 1/Rnow) ). // скорость в апоцентре
// расчет будущей орбиты
local a1 to (newapsis + apoapsis)/2 + body:radius. // новая большая полуось
local v1 to sqrt( body:mu * (2/Ra - 1/a1 ) ).
set deltav to v1 - Va.
print "Burn at apoapsis: " + round(Va) + " -> " + round(v1) + "m/s".
set nd to node(time:seconds + eta:apoapsis, 0, 0, deltav).
add nd.
print "Apoapsis node created.".
}
Самым последним шагом сделаем тонкую подгонку периода орбиты.
function TrimPeriod {
parameter WantedPeriod.
parameter epsilon is 5e-8.
local dt to WantedPeriod - orbit:period.
lock steering to velocity:orbit*dt.
list engines in elist.
local thrustlim to list().
// ставим всем двигателям тягу на ускорение 0.5 м/с^2
from { local i to 0. } until i=elist:length step { set i to i+1. } do {
thrustlim:add(elist[i]:thrustlimit).
set elist[i]:thrustlimit to 0.5*ship:mass/ship:availablethrust.
}
wait until vang(facing:vector,velocity:orbit*dt) < 1.
until abs( (orbit:period - WantedPeriod) / WantedPeriod ) < epsilon {
lock throttle to min( max( abs( orbit:period - WantedPeriod ), 0.05 ), 1 ).
wait 0.01.
}
lock throttle to 0.
// восстанавливаем тягу как было
from { local i to 0. } until i=elist:length step { set i to i+1. } do {
set elist[i]:thrustlimit to thrustlim[i].
}
print "Target period: " + round(WantedPeriod,1) + "s, Delta: " + (orbit:period - WantedPeriod) + " s ".
set ship:control:pilotmainthrottle to 0.
unlock throttle.
}
Наконец, соберём всё это в функцию работы спутника.
function satprogram {
if satmode = 0 {
print "Waiting for LV separation.".
wait until exists("released.txt").
print "LV separation confirmed. ".
wait 20.
nextmode().
}
if satmode = 1 {
print "Aligning for optimal solar panel performance.".
AlignToSun().
wait 15.
stage.
nextmode().
}
if satmode = 2 {
transfernode(7200).
nextmode().
}
if satmode = 3 {
exenode().
nextmode().
}
if satmode = 4 {
wait 10.
aponode(apoapsis).
nextmode().
}
if satmode = 5 {
exenode().
nextmode().
}
if satmode = 6 {
TrimPeriod(7200).
nextmode().
}
if satmode = 7 {
set dish to ship:partsnamed("HighGainAntenna5")[0].
set d to dish:getmodule("ModuleRTAntenna").
d:doevent("activate").
d:setfield("target", Mun).
print "Satellite deployed on operational orbit.".
nextmode().
}
until false AlignToSun().
}
Функция nextmode() будет устанавливать следующий режим работы спутника. Но не только. Дело в том, что при перезагрузке спутник начнёт выполнять всю программу сначала, а этого не хочется. Нужно, чтобы он переходил к первому незаконченному пункту и продолжал с него. Для этого заведём на аппарате вспомогательный файл, в который функция nextmode() будет писать текущий режим работы. При перезагрузке сперва будем читать его, а потом уже запускать satprogram(). Делаем это командой log, которая дописывает строчки в конец файла. Будем передавать ей на печать команды смены режима работы спутника, а писать попросим в файл .ks.
function nextmode {
parameter newmode is satmode+1.
set satmode to newmode.
log "set satmode to " + newmode + "." to "mode.ks".
AlignToSun().
}
Загрузочный файл
Script/boot/bootKommSat.ks:
set volume(1):name to "sat1".
if status="PRELAUNCH" {
copypath("0:/KommSat.ks","satprogram.ks").
log "local satmode to 0." to "mode.ks".
}
runpath("mode.ks").
runpath("satprogram.ks").
satprogram().
Теперь программы носителя и спутника полностью готовы, есть только одно "но": программа спутника занимает более 5 кБ и не вмещается в ёмкость накопителя малого модуля kOS по умолчанию. Поэтому в редакторе нужно поменять ёмкость накопителя на 10 кБ, после чего всю конструкцию можно запускать.
Рисунок 2. Себяшка на орбите.
Видео вывода, крафт (нужны kOS и RemoteTech) и скрипты:
Гайд получился и так довольно длинным, поэтому завершение сети переносится в следующую часть.
Новость отредактировал: Pand5461 - 5 апр 2017 в 01:39
Причина: правка кода