forked from krpc/krpc-library
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Simple_Launch_Script.py
332 lines (287 loc) · 11.4 KB
/
Simple_Launch_Script.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
######################################################################
### Fairly Robust Launch Script
######################################################################
### Kerbart and I are working on a more versatile and pythonic
### launch script that uses the same logic as this one, but I
### thought that it was still worth posting this simpler version
### for perusal (or use!)
######################################################################
import krpc
import time
import math
from pid import PID
from node_executor import execute_next_node
# ----------------------------------------------------------------------------
# Script parameters
# ----------------------------------------------------------------------------
REFRESH_FREQ = 2 # refresh rate in hz
TELEM_DELAY = 5 #number of seconds between telemetry updates
ALL_FUELS = ('LiquidFuel', 'SolidFuel')
MAX_PHYSICS_WARP = 3 # valid values are 0 (none) through 3 (4x)
next_telem_time=time.time()
class MissionParameters(object):
'''
All mission parameters are stored in a single object to easily
pass around
'''
def __init__(self,
max_auto_stage = 0,
orbit_alt = 100000,
grav_turn_finish = 55000,
inclination = 0,
force_roll = True,
roll = 90,
deploy_solar = True,
max_q = 20000):
self.max_auto_stage = max_auto_stage
self.orbit_alt = orbit_alt
self.grav_turn_finish = grav_turn_finish
self.inclination = inclination
self.force_roll = force_roll
self.roll = roll
self.deploy_solar = deploy_solar
self.max_q = max_q
class Telemetry(object):
def __init__(self, vessel, flight):
self.apoapsis = vessel.orbit.apoapsis_altitude
self.periapsis = vessel.orbit.periapsis_altitude
self.time_to_apo = vessel.orbit.time_to_apoapsis
self.time_to_peri = vessel.orbit.time_to_periapsis
self.velocity = vessel.orbit.speed
self.inclination = math.radians(vessel.orbit.inclination)
self.altitude = flight.mean_altitude
self.vertical_speed = flight.vertical_speed
self.lat = flight.latitude
self.lon = flight.longitude
self.q = flight.dynamic_pressure
self.g = flight.g_force
# ----------------------------------------------------------------------------
# Main loop
# ----------------------------------------------------------------------------
def main():
'''
main function is run when you just execute this file, but NOT when you
import it into another file - thus you can choose to call ascent later
to go to space, or just use the other functions in this file.
'''
## Setup KRPC and create a launch_params object with the default settings
conn = krpc.connect(name='Launch')
launch_params = MissionParameters()
ascent(conn,launch_params)
def ascent(conn, launch_params):
'''
Ascent Autopilot function. Goes to space, or dies trying.
'''
#Setup KRPC and PIDs
conn = krpc.connect(name='Launch')
sc = conn.space_center
v = sc.active_vessel
telem=v.flight(v.orbit.body.reference_frame)
thrust_controller = PID(P=.001, I=0.0001, D=0.01)
thrust_controller.ClampI = launch_params.max_q
thrust_controller.setpoint(launch_params.max_q)
#Prepare for Launch
v.auto_pilot.engage()
v.auto_pilot.target_heading=inc_to_heading(launch_params.inclination)
if launch_params.force_roll:
v.auto_pilot.target_roll=launch_params.roll
v.control.throttle=1.0
#Gravity Turn Loop
while apoapsis_way_low(v, launch_params.orbit_alt):
gravturn(conn, launch_params)
autostage(v , launch_params.max_auto_stage)
limitq(conn, thrust_controller)
telemetry(conn)
time.sleep(1.0 / REFRESH_FREQ)
v.control.throttle = 0.0
# Fine Tune APA
v.auto_pilot.disengage()
v.auto_pilot.sas=True
time.sleep(.1)
v.auto_pilot.sas_mode = v.auto_pilot.sas_mode.prograde
v.auto_pilot.wait()
boostAPA(conn, launch_params) #fine tune APA
# Coast Phase
sc.physics_warp_factor = MAX_PHYSICS_WARP
while still_in_atmosphere(conn):
if apoapsis_little_low(v , launch_params.orbit_alt):
sc.physics_warp_factor = 0
boostAPA(conn, launch_params)
sc.physics_warp_factor = MAX_PHYSICS_WARP
telemetry(conn)
time.sleep(1.0 / REFRESH_FREQ)
# Circularization Burn
sc.physics_warp_factor = 0
planCirc(conn)
telemetry(conn)
execute_next_node(conn)
# Finish Up
if launch_params.deploy_solar: v.control.solar_panels=True
telemetry(conn)
v.auto_pilot.sas_mode= v.auto_pilot.sas_mode.prograde
# ----------------------------------------------------------------------------
# staging logic
# ----------------------------------------------------------------------------
def autostage(vessel, MAX_AUTO_STAGE):
'''
activate next stage when there is no fuel left in the current stage
'''
if out_of_stages(vessel, MAX_AUTO_STAGE):
return
res = get_resources(vessel)
interstage = True # flag to check if this is a fuel-less stage
for fueltype in ALL_FUELS:
if out_of_fuel(res, fueltype):
next_stage(vessel)
return
if res.has_resource(fueltype):
interstage = False
if interstage:
next_stage(vessel)
# ----------------------------------------------------------------------------
# guidance routines
# ----------------------------------------------------------------------------
def gravturn(conn, launch_params):
'''
Execute quadratic gravity turn -
based on Robert Penner's easing equations (EaseOut)
'''
vessel = conn.space_center.active_vessel
flight = vessel.flight(vessel.orbit.body.non_rotating_reference_frame)
progress=flight.mean_altitude/launch_params.grav_turn_finish
vessel.auto_pilot.target_pitch= 90-(-90 * progress*(progress-2))
def boostAPA(conn, launch_params):
'''
function to increase Apoapsis using low thrust on a
tight loop with no delay for increased precision.
'''
vessel = conn.space_center.active_vessel
flight = vessel.flight(vessel.orbit.body.non_rotating_reference_frame)
vessel.control.throttle=.2
while apoapsis_little_low(vessel, launch_params.orbit_alt):
autostage(vessel, launch_params.max_auto_stage)
telemetry(conn)
vessel.control.throttle=0
def planCirc(conn):
'''
Plan a Circularization at Apoapsis.
V1 is velocity at apoapsis.
V2 is the velocity at apoapsis of a circular orbit.
Burn time uses Tsiolkovsky rocket equation.
'''
vessel = conn.space_center.active_vessel
ut = conn.space_center.ut
grav_param = vessel.orbit.body.gravitational_parameter
apo = vessel.orbit.apoapsis
sma = vessel.orbit.semi_major_axis
v1 = math.sqrt(grav_param * ((2.0 / apo) - (1.0 / sma)))
v2 = math.sqrt(grav_param * ((2.0 / apo) - (1.0 / apo)))
vessel.control.add_node(ut + vessel.orbit.time_to_apoapsis,
prograde=(v2 - v1))
def inc_to_heading(inc):
'''
Converts desired inclination to a compass heading the autopilot can track
This only works for equatorial launches at the moment!
inc: inclination in degrees
returns: heading in degrees
'''
if inc > 180 or inc < -180:
return 90 #invalid entries get set to 0 inclination
if inc >= 0:
value = 90 - inc
if inc < 0:
value = -(inc - 90)
if value < 0:
value += 360
return value
def limitq(conn, controller):
'''
limits vessel's throttle to stay under MAX_Q using PID controller
'''
vessel = conn.space_center.active_vessel
flight = vessel.flight(vessel.orbit.body.non_rotating_reference_frame)
vessel.control.throttle= controller.update(flight.dynamic_pressure)
# ----------------------------------------------------------------------------
# post telemetry
# ----------------------------------------------------------------------------
def telemetry(conn):
'''
Show telemetry data
TODO: split between creating telemetry data (object? dict?)
and displaying the data. This will make it easier to transition to a
GUI later on. For this reason, no attempts to fit the lines has been
made (yet)
'''
vessel = conn.space_center.active_vessel
flight = vessel.flight(vessel.orbit.body.non_rotating_reference_frame)
global next_telem_time
if time.time() > next_telem_time:
display_telemetry(Telemetry(vessel, flight))
next_telem_time += TELEM_DELAY
def display_telemetry(t):
'''
Take a Telemetry object t and display it in a pleasing way
'''
# define the data to be displayed in as many columns needed
col1 = ('Apoapsis: {apoapsis:8,.0f}',
'Time to apo: {time_to_apo:5,.0f}',
'Altitude: {altitude:6,.0f}',
'Orbital velocity: {velocity:5,.0f}',
'Latitude: {lat:5.1f}',
'Dynamic Pressure: {q:6,.0f}')
col2 = ('Periapsis: {periapsis: 8,.0f}',
'Time to peri: {time_to_peri:5,.0f}',
'Inclination: {inclination: 3.0f}\n',
'Vertical speed: {vertical_speed: 5,.0f}',
'Longitude: {lon:5.1f}\n',
'G-force: {g:4.1f}')
# zip the columns together and display them
print('-' * 60)
for display_line in zip(col1, col2):
print(' '.join(display_line).format(**t.__dict__))
print('-' * 60)
print('\n')
# ----------------------------------------------------------------------------
# Helper functions
# ----------------------------------------------------------------------------
def still_in_atmosphere(conn):
vessel = conn.space_center.active_vessel
flight = vessel.flight(vessel.orbit.body.non_rotating_reference_frame)
return flight.mean_altitude<vessel.orbit.body.atmosphere_depth
def apoapsis_way_low(vessel, ORBIT_ALT):
'''
True if Apoapsis is less than 95% of target apoapsis
'''
return vessel.orbit.apoapsis_altitude<(ORBIT_ALT*.95)
def apoapsis_little_low(vessel, ORBIT_ALT):
'''
True if Apoapsis is less than target apoapsis at all
'''
return vessel.orbit.apoapsis_altitude<ORBIT_ALT
def out_of_stages(vessel, MAX_AUTO_STAGE):
'''
True if no more stages left to activate
'''
return vessel.control.current_stage <= MAX_AUTO_STAGE
def get_resources(vessel):
'''
get resources of the vessel in the decouple stage
'''
return vessel.resources_in_decouple_stage(
vessel.control.current_stage - 1,
cumulative=False)
def out_of_fuel(resource, fueltype):
'''
return True if there is fuel capacity of the fueltype, but no fuel
'''
return resource.max(fueltype) > 0 and resource.amount(fueltype) == 0
def next_stage(vessel):
'''
activate the next stage
'''
vessel.control.activate_next_stage()
# ----------------------------------------------------------------------------
# Activate main loop, assuming we are executing THIS file explicitly.
# ----------------------------------------------------------------------------
if __name__ == "__main__" :
main()