Implement launch azimuth and refine node execution.
This commit is contained in:
parent
f346414b1a
commit
08246d4d83
|
@ -1,5 +1,39 @@
|
||||||
CLEARSCREEN.
|
CLEARSCREEN.
|
||||||
print "=================== BOOTING ===================".
|
print "=================== BOOTING ===================".
|
||||||
switch to 0.
|
|
||||||
|
if HOMECONNECTION:ISCONNECTED {
|
||||||
|
update_scripts().
|
||||||
|
} else {
|
||||||
|
print "No connection to KSC: not updating scripts.".
|
||||||
|
}
|
||||||
|
|
||||||
print "============= BOOT SEQUENCE COMPLETE =============".
|
print "============= BOOT SEQUENCE COMPLETE =============".
|
||||||
|
|
||||||
|
|
||||||
|
function update_scripts {
|
||||||
|
// Copy all .ks-files from the archive to the CPU's internal storage.
|
||||||
|
|
||||||
|
// Get lexicon of existing items (files/directories) and sizes on the internal storage
|
||||||
|
local existing is lexicon().
|
||||||
|
for item in CORE:VOLUME:FILES:VALUES {
|
||||||
|
set existing[item:NAME] to item:SIZE.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if items from the archive match those in the internal storage
|
||||||
|
for item in VOLUME("ARCHIVE"):FILES:VALUES {
|
||||||
|
local skip is false. // kOS doesn't support the 'continue' keyword...
|
||||||
|
|
||||||
|
if not skip and item:NAME[0] = "." set skip to true. // skip linux hidden dirs (e.g. .git)
|
||||||
|
if not skip and item:ISFILE
|
||||||
|
and item:EXTENSION <> "ks" set skip to true. // skip non-script items (e.g. README.md)
|
||||||
|
if not skip and existing:HASKEY(item:NAME)
|
||||||
|
and item:SIZE = existing[item:NAME] set skip to true. // skip if local item is same size
|
||||||
|
|
||||||
|
if not skip {
|
||||||
|
print "Copying " + item:NAME + "..".
|
||||||
|
COPYPATH(PATH(VOLUME("ARCHIVE")):COMBINE(item:NAME), CORE:VOLUME).
|
||||||
|
|
||||||
|
if item:NAME = "boot" reboot. // reboot CPU if boot/ directory was updated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
143
launch.ks
143
launch.ks
|
@ -2,81 +2,124 @@ run once util.
|
||||||
run once vectors.
|
run once vectors.
|
||||||
run once node.
|
run once node.
|
||||||
|
|
||||||
print "====================== LAUNCHING ======================".
|
|
||||||
|
|
||||||
parameter orbit_height is 100_000.
|
function launch {
|
||||||
parameter pitchover_tilt is 20.
|
// Launch to orbit of provided altitude and inclination.
|
||||||
parameter pitchover_altitude is 1000.
|
parameter target_orbit_altitude is 100_000. // meters
|
||||||
parameter pitchover_velocity is 100.
|
parameter target_orbit_inclination is 0. // 0-180 degrees
|
||||||
|
parameter pitchover_tilt is 20. // how many degrees to tilt at pitchover maneuver
|
||||||
|
parameter pitchover_altitude is 1000. // perform pitchover at this altitude
|
||||||
|
parameter pitchover_velocity is 100. // or this velocity
|
||||||
|
|
||||||
|
|
||||||
// Auto-stage when the stage has no solid- and liquid fuel left
|
print "========= LAUNCHING =========".
|
||||||
when (STAGE:SOLIDFUEL + STAGE:LIQUIDFUEL < 1) and STAGE:READY then {
|
print "Target orbital altitude: " + target_orbit_altitude + "m".
|
||||||
print "STAGING (zero fuel)".
|
print "Target orbital inclination: " + target_orbit_inclination + "°".
|
||||||
STAGE.
|
print "Pitchover tilt: " + pitchover_tilt + "°".
|
||||||
return true. // preserve trigger
|
print "Pitchover at: " + pitchover_altitude + "m or " + pitchover_velocity + " m/s".
|
||||||
|
print " ".
|
||||||
|
|
||||||
|
|
||||||
|
// LAUNCH AZIMUTH
|
||||||
|
|
||||||
|
// Since the "center" of an orbit must be at the center of gravity of the body, the latitude of the launch site establishes the minimum absolute orbital inclination.
|
||||||
|
// KSC is almost on the equator, so we're going to always round the latitude towards zero, and accept the inaccuracies it may introduce.
|
||||||
|
local launch_site_latitude is round_towards_zero(SHIP:LATITUDE).
|
||||||
|
local target_orbit_inclination is MAX(target_orbit_inclination, launch_site_latitude).
|
||||||
|
|
||||||
|
// Calculate the launch azimuth; the compass heading we head for when launching to achieve orbit of desired inclination
|
||||||
|
local launch_azimuth is calculate_launch_azimuth(target_orbit_inclination, target_orbit_altitude, launch_site_latitude).
|
||||||
|
|
||||||
|
// If the latitude of the launch site is negative (ship is in the southern hemisphere), launch southwards instead of northwards
|
||||||
|
local southwards is launch_site_latitude < 0.
|
||||||
|
if southwards {
|
||||||
|
set launch_azimuth to 180 - launch_azimuth.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatically deploy solar panels at 0 atmosphere
|
print "Launch site latitude: " + ROUND(SHIP:LATITUDE, 3) + "° (~" + launch_site_latitude + "°)".
|
||||||
when SHIP:DYNAMICPRESSURE = 0 then {
|
print "Available orbital inclination: " + target_orbit_inclination + "°".
|
||||||
print "Deploying solar panels (zero pressure)".
|
print "Launch azimuth: " + ROUND(launch_azimuth, 3) + "°".
|
||||||
PANELS ON.
|
print "Launch southwards: " + southwards.
|
||||||
}
|
|
||||||
|
|
||||||
// Disable engine gimbal when engine is off
|
|
||||||
on (not THROTTLE) { // using not to cast to boolean
|
|
||||||
list engines in engines_list.
|
|
||||||
for engine in engines_list {
|
|
||||||
if engine:HASGIMBAL set engine:GIMBAL:LOCK to not THROTTLE.
|
|
||||||
}
|
|
||||||
return true. // preserve trigger
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// VERTICAL CLIMB
|
print "--- VERTICAL CLIMB ---".
|
||||||
|
|
||||||
print "VERTICAL CLIMB to " + pitchover_altitude + "m or " + pitchover_velocity + "m/s".
|
|
||||||
SAS OFF.
|
SAS OFF.
|
||||||
set NAVMODE to "SURFACE".
|
set NAVMODE to "SURFACE".
|
||||||
|
lock STEERING to HEADING(launch_azimuth, 90). // roll to launch azimuth
|
||||||
lock THROTTLE to 1.0.
|
lock THROTTLE to 1.0.
|
||||||
lock STEERING to HEADING(90,90).
|
STAGE.
|
||||||
|
|
||||||
// Once a certain altitude is reached, a slight turn is made, called the pitchover maneuver
|
// Enable auto-stage when the maximum engine thrust changes (i.e. some engines ran out of fuel)
|
||||||
|
on SHIP:MAXTHRUSTAT(0) {
|
||||||
|
when STAGE:READY then {
|
||||||
|
print "STAGING".
|
||||||
|
STAGE.
|
||||||
|
}.
|
||||||
|
return true. // preserve trigger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once a certain altitude (or velocity) is reached, a slight turn is made, called the pitchover maneuver
|
||||||
wait until SHIP:ALTITUDE > pitchover_altitude
|
wait until SHIP:ALTITUDE > pitchover_altitude
|
||||||
or SHIP:VELOCITY:SURFACE:MAG > pitchover_velocity.
|
or SHIP:VELOCITY:SURFACE:MAG > pitchover_velocity.
|
||||||
|
|
||||||
|
|
||||||
// PITCHOVER MANEUVER
|
print "--- PITCHOVER ---".
|
||||||
|
lock STEERING to HEADING(launch_azimuth, 90-pitchover_tilt).
|
||||||
|
|
||||||
print "PITCHOVER by " + pitchover_tilt + "°".
|
wait until actual_prograde_pitch() > pitchover_tilt. // wait until the prograde "catches up" to our tilted heading
|
||||||
lock STEERING to HEADING(90,90-pitchover_tilt).
|
|
||||||
|
|
||||||
wait until actual_prograde_pitch() > pitchover_tilt.
|
|
||||||
|
|
||||||
|
|
||||||
// GRAVITY TURN
|
print "--- GRAVITY TURN ---".
|
||||||
|
// TODO: the angle of the launch azimuth will not account for the fact that our compass will change as we move north/south.
|
||||||
|
lock STEERING to HEADING(launch_azimuth, 90-actual_prograde_pitch()). // Follow prograde pitch to get 0 deg angle of attack, but force compass heading at launch azimuth.
|
||||||
|
|
||||||
print "GRAVITY TURN".
|
wait until APOAPSIS > target_orbit_altitude.
|
||||||
lock STEERING to HEADING(90, 90-actual_prograde_pitch()). // follow prograde to get 0 deg angle of attack, but force compass heading 90 (east)
|
|
||||||
|
|
||||||
wait until APOAPSIS > orbit_height.
|
|
||||||
lock THROTTLE TO 0.
|
lock THROTTLE TO 0.
|
||||||
unlock STEERING.
|
unlock_control().
|
||||||
|
|
||||||
|
|
||||||
// CREATE AND EXECUTE CIRCULARIZATION MANEUVER NODE
|
print "--- CIRCULARIZE ---".
|
||||||
create_circularization_node().
|
print "Waiting for ship to leave the atmosphere..".
|
||||||
|
set KUNIVERSE:TIMEWARP:RATE to 4.
|
||||||
|
wait until SHIP:DYNAMICPRESSURE = 0. // don't create maneuver node until we are out of the atmosphere - otherwise the apoapsis altitude and eta will change due to drag
|
||||||
|
KUNIVERSE:TIMEWARP:CANCELWARP().
|
||||||
|
|
||||||
|
print "Deploying solar panels".
|
||||||
|
PANELS ON.
|
||||||
|
|
||||||
|
// Create maneuver node that will circularize the orbit.
|
||||||
|
// NOTE: Potential errors in the inclination are not fixed, since we are most likely going to change our orbit, which will make the inclination change cheaper later on.
|
||||||
|
local node is NODE(TIME:SECONDS + ETA:APOAPSIS, 0, 0, 0).
|
||||||
|
|
||||||
|
// Burn magnitude in the prograde direction is the difference between the required velocity to acheive orbit at apoapsis altitude and our velocity at apoapsis
|
||||||
|
set node:PROGRADE to orbital_velocity(APOAPSIS) - VELOCITYAT(SHIP, TIME:SECONDS + ETA:APOAPSIS):ORBIT:MAG.
|
||||||
|
|
||||||
|
add node.
|
||||||
execute_node().
|
execute_node().
|
||||||
|
|
||||||
|
|
||||||
// EPILOGUE
|
print "========= LAUNCH SEQUENCE COMPLETE =========".
|
||||||
|
unlock_control().
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that the throttle will be 0 when execution stops
|
|
||||||
set SHIP:CONTROL:PILOTMAINTHROTTLE to 0.
|
|
||||||
|
|
||||||
// Ensure that the player is not locked out of control.
|
function calculate_launch_azimuth {
|
||||||
set SHIP:CONTROL:NEUTRALIZE to true.
|
// Calculate the launch azimuth; the compass heading we head for when launching to achieve orbit of desired inclination.
|
||||||
unlock STEERING.
|
// http://www.orbiterwiki.org/wiki/Launch_Azimuth
|
||||||
unlock THROTTLE.
|
// https://www.princeton.edu/~stengel/MAE342Lecture4.pdf
|
||||||
|
parameter target_orbit_inclination.
|
||||||
|
parameter target_orbit_altitude. // to compensate for the rotation of the body, we need to know the velocity of the target orbit, which is calculated from its altitude
|
||||||
|
parameter launch_site_latitude.
|
||||||
|
|
||||||
print "====================== LAUNCH SEQUENCE COMPLETE ======================".
|
local inertial_azimuth is ARCSIN(COS(target_orbit_inclination) / COS(launch_site_latitude)). // azimuth in inertial space, that is, disregarding the rotation of the body
|
||||||
|
|
||||||
|
local equatorial_rotational_velocity is (2 * CONSTANT:PI * BODY:RADIUS) / BODY:ROTATIONPERIOD.
|
||||||
|
local target_orbit_velocity is orbital_velocity(target_orbit_altitude). // velocity of target orbit
|
||||||
|
|
||||||
|
local launch_vector_x_component is target_orbit_velocity * SIN(inertial_azimuth) - equatorial_rotational_velocity * COS(launch_site_latitude).
|
||||||
|
local launch_vector_y_component is target_orbit_velocity * COS(inertial_azimuth).
|
||||||
|
return ARCTAN2(launch_vector_x_component, launch_vector_y_component).
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
launch(100_000, 0).
|
||||||
|
|
81
node.ks
81
node.ks
|
@ -1,72 +1,57 @@
|
||||||
run once util.
|
run once util.
|
||||||
|
|
||||||
|
|
||||||
|
function estimated_burn_duration {
|
||||||
|
// Calculate estimated burn duration for a maneuver node
|
||||||
|
// https://en.wikipedia.org/wiki/Tsiolkovsky_rocket_equation
|
||||||
|
// https://space.stackexchange.com/questions/27375/how-do-i-calculate-a-rockets-burn-time-from-required-velocity
|
||||||
|
parameter node is NEXTNODE.
|
||||||
|
|
||||||
|
local exhaust_velocity is isp_sum() * (CONSTANT:G * KERBIN:MASS).
|
||||||
|
return ((SHIP:MASS * exhaust_velocity) / SHIP:MAXTHRUST) * (1 - CONSTANT:E^(-node:DELTAV:MAG/exhaust_velocity)).
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function execute_node {
|
function execute_node {
|
||||||
parameter node is NEXTNODE.
|
parameter node is NEXTNODE.
|
||||||
|
parameter precision is 0.05. // m/s delta-v
|
||||||
|
|
||||||
print "EXECUTE MANEUVER NODE".
|
local burn_duration is estimated_burn_duration(node).
|
||||||
|
|
||||||
// Calculate estimated burn time
|
print "=== EXECUTE MANEUVER NODE ===".
|
||||||
// https://en.wikipedia.org/wiki/Tsiolkovsky_rocket_equation
|
print "Estimated burn duration: " + ROUND(burn_duration, 1) + "s".
|
||||||
// https://space.stackexchange.com/questions/27375/how-do-i-calculate-a-rockets-burn-time-from-required-velocity
|
|
||||||
local isp_sum is 0.
|
|
||||||
list ENGINES in engines_list.
|
|
||||||
for engine in engines_list {
|
|
||||||
if engine:STAGE = STAGE:NUMBER set ISP_sum to ISP_sum + engine:VISP.
|
|
||||||
}
|
|
||||||
|
|
||||||
local ve is ISP_sum * (CONSTANT:G * KERBIN:MASS). // exhaust velocity
|
print "Aligning ship with burn vector..".
|
||||||
local burn_time is ((SHIP:MASS * ve) / SHIP:MAXTHRUST) * (1 - CONSTANT:E^(-node:BURNVECTOR:MAG/ve)).
|
|
||||||
|
|
||||||
print "Estimated burn time: " + ROUND(burn_time).
|
|
||||||
|
|
||||||
print "Aligning ship with burn vector".
|
|
||||||
SAS OFF.
|
SAS OFF.
|
||||||
lock STEERING to node:BURNVECTOR.
|
lock STEERING to node:DELTAV.
|
||||||
wait until VANG(SHIP:FACING:VECTOR, node:BURNVECTOR) < 0.1.
|
wait until VANG(SHIP:FACING:VECTOR, node:DELTAV) < 0.1.
|
||||||
|
|
||||||
print "Initializing warp".
|
print "Initializing warp".
|
||||||
unlock STEERING. // TODO: Doesnt work?
|
unlock_control().
|
||||||
warp_for(MAX(0, node:ETA - (burn_time/2) - 5)). // Warp until 5s before node
|
warp_for(MAX(0, node:ETA - (burn_duration/2) - 5)). // warp until 5s before node
|
||||||
|
|
||||||
print "Approaching".
|
print "Approaching".
|
||||||
lock STEERING to node:BURNVECTOR.
|
lock STEERING to node:DELTAV.
|
||||||
wait until node:ETA <= burn_time/2.
|
wait until node:ETA <= ROUND(burn_duration/2).
|
||||||
|
|
||||||
print "Burn!".
|
print "Burn!".
|
||||||
lock THROTTLE to 1.0.
|
lock THROTTLE to 1.0.
|
||||||
|
|
||||||
//throttle is 100% until there is less than 1 second of time left to burn
|
// Decrease throttle linearly when the burn duration is less than 1 second
|
||||||
//when there is less than 1 second - decrease the throttle linearly
|
wait until estimated_burn_duration(node) <= 1.
|
||||||
// TODO: Smooth at the end
|
lock THROTTLE to MAX(0.01, estimated_burn_duration(node)). // ensure we always finish by burning with at least 1% power
|
||||||
wait until node:DELTAV:MAG < 5.0. // TODO
|
|
||||||
|
wait until node:DELTAV:MAG < precision.
|
||||||
set THROTTLE to 0.0.
|
set THROTTLE to 0.0.
|
||||||
|
|
||||||
print "DONE! Removing node and unlocking steering".
|
print "=== MANEUVER NODE EXECUTED ===".
|
||||||
|
print ROUND(node:DELTAV:MAG, 3) + "m/s delta-v remaining".
|
||||||
|
unlock_control().
|
||||||
|
wait 1.
|
||||||
remove node.
|
remove node.
|
||||||
|
|
||||||
unlock STEERING.
|
|
||||||
unlock THROTTLE.
|
|
||||||
SET SHIP:CONTROL:PILOTMAINTHROTTLE TO 0.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function create_circularization_node {
|
function change_slash_set_orbit {
|
||||||
// parameter where.
|
|
||||||
|
|
||||||
print "CREATE CIRCULARIZATION NODE".
|
|
||||||
|
|
||||||
local node is NODE(TIME:SECONDS + ETA:APOAPSIS, 0, 0, 0).
|
|
||||||
add node.
|
|
||||||
|
|
||||||
// Calculate required velocity to acheive orbit at altitude equal to apoapsis
|
|
||||||
// https://en.wikipedia.org/wiki/Orbital_speed#Mean_orbital_speed
|
|
||||||
local orbital_speed is SQRT((CONSTANT:G * ORBIT:BODY:MASS) / (ORBIT:BODY:RADIUS + APOAPSIS)).
|
|
||||||
|
|
||||||
// Calculate the difference between the required velocity and our velocity at apoapsis
|
|
||||||
set node:PROGRADE to orbital_speed - VELOCITYAT(SHIP, TIME:SECONDS + ETA:APOAPSIS):ORBIT:MAG.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function create_orbitchange_node {
|
|
||||||
parameter what.
|
parameter what.
|
||||||
}
|
}
|
57
util.ks
57
util.ks
|
@ -1,11 +1,64 @@
|
||||||
function warp_to {
|
function warp_to {
|
||||||
parameter timestamp.
|
parameter timestamp.
|
||||||
|
|
||||||
KUNIVERSE:TIMEWARP:WARPTO(timestamp). // todo: improve.
|
KUNIVERSE:TIMEWARP:WARPTO(timestamp). // TODO: improve.
|
||||||
|
// wait until TIME >= timestamp. // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
function warp_for {
|
function warp_for {
|
||||||
parameter seconds.
|
parameter seconds.
|
||||||
|
|
||||||
return warp_to(TIME:SECONDS + seconds).
|
return warp_to(TIME:SECONDS + seconds).
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function isp_sum {
|
||||||
|
local sum is 0.
|
||||||
|
list ENGINES in engines_list.
|
||||||
|
for engine in engines_list {
|
||||||
|
if engine:STAGE = STAGE:NUMBER set sum to sum + engine:VACUUMISP.
|
||||||
|
}
|
||||||
|
return sum.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function round_towards_zero {
|
||||||
|
parameter n.
|
||||||
|
|
||||||
|
if n < 0 return CEILING(n).
|
||||||
|
return FLOOR(n).
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function orbital_velocity {
|
||||||
|
// Calculate the required velocity to acheive orbit at the provided altitude.
|
||||||
|
// https://en.wikipedia.org/wiki/Orbital_speed#Mean_orbital_speed
|
||||||
|
// http://www.orbiterwiki.org/wiki/Front_Cover_Equations
|
||||||
|
parameter altitude.
|
||||||
|
|
||||||
|
return SQRT(BODY:MU / (BODY:RADIUS + altitude)). // BODY:MU = CONSTANT:G * BODY:MASS
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function unlock_control {
|
||||||
|
// Ensure that the throttle will be 0 when execution stops
|
||||||
|
set SHIP:CONTROL:PILOTMAINTHROTTLE to 0.
|
||||||
|
|
||||||
|
// Ensure that the player is not locked out of control.
|
||||||
|
set SHIP:CONTROL:NEUTRALIZE to true.
|
||||||
|
unlock STEERING.
|
||||||
|
unlock THROTTLE.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function disable_engine_gimbal {
|
||||||
|
// Disable engine gimbal when engine is off
|
||||||
|
on (THROTTLE = 0) {
|
||||||
|
list ENGINES in engines_list.
|
||||||
|
for engine in engines_list {
|
||||||
|
if engine:HASGIMBAL set engine:GIMBAL:LOCK to (not THROTTLE).
|
||||||
|
}
|
||||||
|
return true. // preserve trigger
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue