diff --git a/boot/default.ks b/boot/default.ks index 90ebf22..47b7b6f 100755 --- a/boot/default.ks +++ b/boot/default.ks @@ -1,5 +1,39 @@ CLEARSCREEN. print "=================== BOOTING ===================". -switch to 0. -print "============= BOOT SEQUENCE COMPLETE =============". \ No newline at end of file +if HOMECONNECTION:ISCONNECTED { + update_scripts(). +} else { + print "No connection to KSC: not updating scripts.". +} + +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 + } + } +} diff --git a/launch.ks b/launch.ks index 1865524..a3c976c 100755 --- a/launch.ks +++ b/launch.ks @@ -2,81 +2,124 @@ run once util. run once vectors. run once node. -print "====================== LAUNCHING ======================". -parameter orbit_height is 100_000. -parameter pitchover_tilt is 20. -parameter pitchover_altitude is 1000. -parameter pitchover_velocity is 100. - - -// Auto-stage when the stage has no solid- and liquid fuel left -when (STAGE:SOLIDFUEL + STAGE:LIQUIDFUEL < 1) and STAGE:READY then { - print "STAGING (zero fuel)". - STAGE. - return true. // preserve trigger -} - -// 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 -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. +function launch { + // Launch to orbit of provided altitude and inclination. + parameter target_orbit_altitude is 100_000. // meters + 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 + + + print "========= LAUNCHING =========". + print "Target orbital altitude: " + target_orbit_altitude + "m". + print "Target orbital inclination: " + target_orbit_inclination + "°". + print "Pitchover tilt: " + pitchover_tilt + "°". + 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. } - 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. + + 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 -print "VERTICAL CLIMB to " + pitchover_altitude + "m or " + pitchover_velocity + "m/s". -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 -wait until SHIP:ALTITUDE > pitchover_altitude - or SHIP:VELOCITY:SURFACE:MAG > pitchover_velocity. - - -// PITCHOVER MANEUVER - -print "PITCHOVER by " + pitchover_tilt + "°". -lock STEERING to HEADING(90,90-pitchover_tilt). - -wait until actual_prograde_pitch() > pitchover_tilt. + 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). +} -// 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 ======================". \ No newline at end of file +launch(100_000, 0). diff --git a/node.ks b/node.ks index eb67388..ca1b9f3 100755 --- a/node.ks +++ b/node.ks @@ -1,72 +1,57 @@ 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 { 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 - // 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 - 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 - 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". + print "=== EXECUTE MANEUVER NODE ===". + print "Estimated burn duration: " + ROUND(burn_duration, 1) + "s". + + print "Aligning ship with burn vector..". SAS OFF. - lock STEERING to node:BURNVECTOR. - wait until VANG(SHIP:FACING:VECTOR, node:BURNVECTOR) < 0.1. + lock STEERING to node:DELTAV. + wait until VANG(SHIP:FACING:VECTOR, node:DELTAV) < 0.1. print "Initializing warp". - unlock STEERING. // TODO: Doesnt work? - warp_for(MAX(0, node:ETA - (burn_time/2) - 5)). // Warp until 5s before node + unlock_control(). + warp_for(MAX(0, node:ETA - (burn_duration/2) - 5)). // warp until 5s before node print "Approaching". - lock STEERING to node:BURNVECTOR. - wait until node:ETA <= burn_time/2. + lock STEERING to node:DELTAV. + wait until node:ETA <= ROUND(burn_duration/2). print "Burn!". lock THROTTLE to 1.0. - //throttle is 100% until there is less than 1 second of time left to burn - //when there is less than 1 second - decrease the throttle linearly - // TODO: Smooth at the end - wait until node:DELTAV:MAG < 5.0. // TODO + // Decrease throttle linearly when the burn duration is less than 1 second + wait until estimated_burn_duration(node) <= 1. + 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 < precision. 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. - - unlock STEERING. - unlock THROTTLE. - SET SHIP:CONTROL:PILOTMAINTHROTTLE TO 0. } -function create_circularization_node { - // 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 { +function change_slash_set_orbit { parameter what. -} \ No newline at end of file +} diff --git a/test.ks b/test.ks deleted file mode 100755 index 1d72380..0000000 --- a/test.ks +++ /dev/null @@ -1,3 +0,0 @@ -print "TEST". - -run once vectors. diff --git a/util.ks b/util.ks index 5685b41..b780fc9 100755 --- a/util.ks +++ b/util.ks @@ -1,11 +1,64 @@ function warp_to { parameter timestamp. - KUNIVERSE:TIMEWARP:WARPTO(timestamp). // todo: improve. + KUNIVERSE:TIMEWARP:WARPTO(timestamp). // TODO: improve. + // wait until TIME >= timestamp. // TODO } function warp_for { parameter seconds. - return warp_to(TIME:SECONDS + seconds). -} \ No newline at end of file +} + + + +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 + } +} diff --git a/vectors.ks b/vectors.ks index 311edd4..57533b0 100755 --- a/vectors.ks +++ b/vectors.ks @@ -18,26 +18,26 @@ lock actual_prograde_pitch to VANG(actual_prograde:VECTOR, UP:VECTOR). -// // the following are all vectors, mainly for use in the roll, pitch, and angle of attack calculations -// lock rightrotation to ship:facing*r(0,90,0). -// lock right to rightrotation:vector. //right and left are directly along wings -// lock left to (-1)*right. -// lock up to ship:up:vector. //up and down are skyward and groundward -// lock down to (-1)*up. -// lock fore to ship:facing:vector. //fore and aft point to the nose and tail -// lock aft to (-1)*fore. -// lock righthor to vcrs(up,fore). //right and left horizons -// lock lefthor to (-1)*righthor. -// lock forehor to vcrs(righthor,up). //forward and backward horizons -// lock afthor to (-1)*forehor. -// lock top to vcrs(fore,right). //above the cockpit, through the floor -// lock bottom to (-1)*top. - -// // the following are all angles, useful for control programs -// lock absaoa to vang(fore,srfprograde:vector). //absolute angle of attack -// lock aoa to vang(top,srfprograde:vector)-90. //pitch component of angle of attack -// lock sideslip to vang(right,srfprograde:vector)-90. //yaw component of aoa -// lock rollangle to vang(right,righthor)*((90-vang(top,righthor))/abs(90-vang(top,righthor))). //roll angle, 0 at level flight -// lock pitchangle to vang(fore,forehor)*((90-vang(fore,up))/abs(90-vang(fore,up))). //pitch angle, 0 at level flight -// lock glideslope to vang(srfprograde:vector,forehor)*((90-vang(srfprograde:vector,up))/abs(90-vang(srfprograde:vector,up))). -// lock ascentangle to vang(srfprograde:vector, forehor). //angle of surface prograde above horizon \ No newline at end of file +// // the following are all vectors, mainly for use in the roll, pitch, and angle of attack calculations +// lock rightrotation to ship:facing*r(0,90,0). +// lock right to rightrotation:vector. //right and left are directly along wings +// lock left to (-1)*right. +// lock up to ship:up:vector. //up and down are skyward and groundward +// lock down to (-1)*up. +// lock fore to ship:facing:vector. //fore and aft point to the nose and tail +// lock aft to (-1)*fore. +// lock righthor to vcrs(up,fore). //right and left horizons +// lock lefthor to (-1)*righthor. +// lock forehor to vcrs(righthor,up). //forward and backward horizons +// lock afthor to (-1)*forehor. +// lock top to vcrs(fore,right). //above the cockpit, through the floor +// lock bottom to (-1)*top. + +// // the following are all angles, useful for control programs +// lock absaoa to vang(fore,srfprograde:vector). //absolute angle of attack +// lock aoa to vang(top,srfprograde:vector)-90. //pitch component of angle of attack +// lock sideslip to vang(right,srfprograde:vector)-90. //yaw component of aoa +// lock rollangle to vang(right,righthor)*((90-vang(top,righthor))/abs(90-vang(top,righthor))). //roll angle, 0 at level flight +// lock pitchangle to vang(fore,forehor)*((90-vang(fore,up))/abs(90-vang(fore,up))). //pitch angle, 0 at level flight +// lock glideslope to vang(srfprograde:vector,forehor)*((90-vang(srfprograde:vector,up))/abs(90-vang(srfprograde:vector,up))). +// lock ascentangle to vang(srfprograde:vector, forehor). //angle of surface prograde above horizon