Implement launch azimuth and refine node execution.

This commit is contained in:
Casper V. Kristensen 2018-07-26 04:06:14 +02:00
parent f346414b1a
commit 08246d4d83
Signed by: caspervk
GPG key ID: B1156723DB3BDDA8
6 changed files with 263 additions and 151 deletions

View file

@ -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
}
}
}

173
launch.ks
View file

@ -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 " ".
// Automatically deploy solar panels at 0 atmosphere
when SHIP:DYNAMICPRESSURE = 0 then {
print "Deploying solar panels (zero pressure)".
PANELS ON.
}
// Disable engine gimbal when engine is off // LAUNCH AZIMUTH
on (not THROTTLE) { // using not to cast to boolean
list engines in engines_list. // 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.
for engine in engines_list { // KSC is almost on the equator, so we're going to always round the latitude towards zero, and accept the inaccuracies it may introduce.
if engine:HASGIMBAL set engine:GIMBAL:LOCK to not THROTTLE. 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.
} }
return true. // preserve trigger
print "Launch site latitude: " + ROUND(SHIP:LATITUDE, 3) + "° (~" + launch_site_latitude + "°)".
print "Available orbital inclination: " + target_orbit_inclination + "°".
print "Launch azimuth: " + ROUND(launch_azimuth, 3) + "°".
print "Launch southwards: " + southwards.
print "--- VERTICAL CLIMB ---".
SAS OFF.
set NAVMODE to "SURFACE".
lock STEERING to HEADING(launch_azimuth, 90). // roll to launch azimuth
lock THROTTLE to 1.0.
STAGE.
// 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
or SHIP:VELOCITY:SURFACE:MAG > pitchover_velocity.
print "--- PITCHOVER ---".
lock STEERING to HEADING(launch_azimuth, 90-pitchover_tilt).
wait until actual_prograde_pitch() > pitchover_tilt. // wait until the prograde "catches up" to our tilted heading
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.
wait until APOAPSIS > target_orbit_altitude.
lock THROTTLE TO 0.
unlock_control().
print "--- CIRCULARIZE ---".
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().
print "========= LAUNCH SEQUENCE COMPLETE =========".
unlock_control().
} }
// VERTICAL CLIMB function calculate_launch_azimuth {
// Calculate the launch azimuth; the compass heading we head for when launching to achieve orbit of desired inclination.
// http://www.orbiterwiki.org/wiki/Launch_Azimuth
// 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 "VERTICAL CLIMB to " + pitchover_altitude + "m or " + pitchover_velocity + "m/s". 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
SAS OFF.
set NAVMODE to "SURFACE".
lock THROTTLE to 1.0.
lock STEERING to HEADING(90,90).
// Once a certain altitude is reached, a slight turn is made, called the pitchover maneuver local equatorial_rotational_velocity is (2 * CONSTANT:PI * BODY:RADIUS) / BODY:ROTATIONPERIOD.
wait until SHIP:ALTITUDE > pitchover_altitude local target_orbit_velocity is orbital_velocity(target_orbit_altitude). // velocity of target orbit
or SHIP:VELOCITY:SURFACE:MAG > pitchover_velocity.
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).
}
// PITCHOVER MANEUVER launch(100_000, 0).
print "PITCHOVER by " + pitchover_tilt + "°".
lock STEERING to HEADING(90,90-pitchover_tilt).
wait until actual_prograde_pitch() > pitchover_tilt.
// GRAVITY TURN
print "GRAVITY TURN".
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.
unlock STEERING.
// CREATE AND EXECUTE CIRCULARIZATION MANEUVER NODE
create_circularization_node().
execute_node().
// EPILOGUE
// 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.
print "====================== LAUNCH SEQUENCE COMPLETE ======================".

81
node.ks
View file

@ -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.
} }

View file

@ -1,3 +0,0 @@
print "TEST".
run once vectors.

57
util.ks
View file

@ -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
}
}