def retract_panels(conn: Client): vessel = conn.space_center.active_vessel dialog = StatusDialog(conn) dialog.status_update("Retract solar/radiator panels") for panel in vessel.parts.solar_panels + vessel.parts.radiators: if panel.deployable: panel.deployed = False
def deploy_legs(conn: Client): vessel = conn.space_center.active_vessel dialog = StatusDialog(conn) dialog.status_update("Deploy legs") for leg in vessel.parts.legs: if leg.deployable: leg.deployed = True
def warp_for_longtitude(conn: Client, longtitude: float): dialog = StatusDialog(conn) vessel = conn.space_center.active_vessel body = vessel.orbit.body current_longtitude = (body.rotation_angle * 180 / math.pi + vessel.flight().longitude) % 360 longtitude_ut = (longtitude - current_longtitude) % 360 / ( body.rotational_speed * 180 / math.pi) + conn.space_center.ut dialog.status_update("Waiting for launch timing") lead_time = 5 conn.space_center.warp_to(longtitude_ut - lead_time)
def match_plane_with_target(conn: Client): """match plane with target Extended description here Args: conn: kRPC connection Returns: return nothing, return when procedure finished """ # Set up dialog dialog = StatusDialog(conn) # check target vessel = conn.space_center.active_vessel target = conn.space_center.target_vessel if not target: target = conn.space_center.target_body if not target: return v_orbit = vessel.orbit t_orbit = target.orbit # find sooner timing ut_an = v_orbit.ut_at_true_anomaly(v_orbit.true_anomaly_at_an(t_orbit)) ut_dn = v_orbit.ut_at_true_anomaly(v_orbit.true_anomaly_at_dn(t_orbit)) if ut_an < ut_dn: ascending = True node_ut = ut_an else: ascending = False node_ut = ut_dn # calculate plane change burn speed_at_node = v_orbit.orbital_speed_at(node_ut) rel_inc = v_orbit.relative_inclination(t_orbit) normal = speed_at_node * math.sin(rel_inc) prograde = speed_at_node * math.cos(rel_inc) - speed_at_node if ascending: normal *= -1 node_desc = "ascending node" if ascending else "decending node" dialog.status_update( f"Match plane with {target.name} on {node_desc} (ut: {node_ut: .2f})" ) vessel.control.add_node(node_ut, normal=normal, prograde=prograde) execute_next_node(conn)
def hohmann_transfer_to_target(conn: Client) -> None: """send active vessel into hohmann transfer orbit to the target. Extended description of function. Args: conn: kRPC connection Returns: return nothing, return when procedure finished """ vessel = conn.space_center.active_vessel # setup stream ut = conn.add_stream(getattr, conn.space_center, "ut") # Set up dialog dialog = StatusDialog(conn) dialog.status_update("calculating hohmann transfer orbit to target") # check target object target = None # target_type = None if conn.space_center.target_body: target = conn.space_center.target_body # target_type = "CelestialBody" elif conn.space_center.target_vessel: target = conn.space_center.target_vessel # target_type = "Vessel" else: return # check if vessel and target is orbiting of same body if vessel.orbit.body != target.orbit.body: return phase_angle = get_phase_angle(vessel, target) transfer_time = time_to_hohmann_transfer_at_phase_angle( vessel, target, ut(), phase_angle) hohmann_transfer(vessel, target, transfer_time) execute_next_node(conn)
def kill_horizontal_velocity(conn: Client, use_sas: bool = True): vessel = conn.space_center.active_vessel # Set up dialog and stream dialog = StatusDialog(conn) ref_frame = conn.space_center.ReferenceFrame.create_hybrid( position=vessel.orbit.body.reference_frame, rotation=vessel.surface_reference_frame, ) flight = vessel.flight(ref_frame) mass = conn.add_stream(getattr, vessel, "mass") available_thrust = conn.add_stream(getattr, vessel, "available_thrust") altitude = conn.add_stream(getattr, flight, "surface_altitude") speed = conn.add_stream(getattr, flight, "speed") vertical_speed = conn.add_stream(getattr, flight, "vertical_speed") horizontal_speed = conn.add_stream(getattr, flight, "horizontal_speed") dialog.status_update("kill horizontal velocity") if use_sas: vessel.control.sas = True vessel.control.sas_mode = vessel.control.sas_mode.retrograde while (angle_between( vessel.flight( vessel.surface_velocity_reference_frame).direction, (0, -1, 0), ) > 5 / 180 * math.pi): time.sleep(0.1) else: vessel.auto_pilot.engage() vessel.auto_pilot.reference_frame = ( vessel.surface_velocity_reference_frame) vessel.auto_pilot.target_direction = (0, -1, 0) vessel.auto_pilot.wait() while True: a100 = available_thrust() / mass() dialog.status_update( f"kill horizontal velocity: Alt: {altitude(): 5.3f}, Speed {speed(): 5.3f} m/s (H: {horizontal_speed(): 5.3f}, V: {vertical_speed(): 5.3f})" ) if horizontal_speed() > 0.1: vessel.control.throttle = max(0, min(1.0, speed() / a100)) else: vessel.control.throttle = 0 break
def execute_next_node( conn: Client, auto_stage: bool = True, stop_stage: int = 0 ) -> None: vessel = conn.space_center.active_vessel nodes = vessel.control.nodes if not nodes: return node = nodes[0] # check manuever hold capability use_sas = False try: vessel.control.sas = True vessel.control.sas_mode = vessel.control.sas_mode.maneuver use_sas = True except Exception: pass vessel.control.sas = False # setup dialog and streams dialog = StatusDialog(conn) ut = conn.add_stream(getattr, conn.space_center, "ut") direction = conn.add_stream( getattr, vessel.flight(node.reference_frame), "direction" ) # setup autostaging if auto_stage: set_autostaging(conn, stop_stage=stop_stage) # Calculate burn time (using rocket equation) F = vessel.available_thrust Isp = vessel.specific_impulse * 9.82 m0 = vessel.mass m1 = m0 / math.exp(node.delta_v / Isp) flow_rate = F / Isp burn_time = (m0 - m1) / flow_rate # Orientate ship dialog.status_update("Orientating ship for next burn") if use_sas: vessel.control.sas = True vessel.control.sas_mode = vessel.control.sas_mode.maneuver while angle_between(direction(), (0, 1, 0)) > 0.5 / 180 * math.pi: time.sleep(0.1) else: vessel.auto_pilot.engage() vessel.auto_pilot.reference_frame = node.reference_frame vessel.auto_pilot.target_direction = (0, 1, 0) while angle_between(direction(), (0, 1, 0)) > 1 / 180 * math.pi: time.sleep(0.1) # Wait until burn dialog.status_update("Waiting until burn time") burn_ut = node.ut - (burn_time / 2.0) lead_time = 5 conn.space_center.warp_to(burn_ut - lead_time) # Execute burn dialog.status_update("Ready to execute burn") while burn_ut - ut() > 0: pass dialog.status_update("Executing burn") vessel.control.throttle = 1.0 remaining_delta_v = conn.add_stream(getattr, node, "remaining_delta_v") min_delta_v = remaining_delta_v() state_fine_tuning = False point_passed = False while remaining_delta_v() > 0.1 and not point_passed: a100 = vessel.available_thrust / vessel.mass if a100 == 0: if auto_stage: time.sleep(0.05) continue else: break throttle = max(0.05, min(1.0, remaining_delta_v() / a100)) if throttle < 1.0: if not state_fine_tuning: dialog.status_update("Fine tuning") state_fine_tuning = True vessel.control.throttle = throttle if min_delta_v < remaining_delta_v(): point_passed = True else: min_delta_v = remaining_delta_v() pass vessel.control.throttle = 0.0 remaining_delta_v.remove() node.remove() if auto_stage: unset_autostaging() vessel.control.sas = True time.sleep(1) vessel.control.sas = False vessel.auto_pilot.disengage() return
def vertical_landing( conn: Client, landing_speed: float = 5.0, auto_stage: bool = True, stop_stage: int = 0, target_lat: Optional[float] = None, target_lon: Optional[float] = None, deploy_legs_on_entry: bool = True, retract_palens_on_entry: bool = True, use_rcs_on_entry: bool = False, entry_attitude: str = "Retrograde", entry_attitude_func: Callable[[Vessel], None] = None, deploy_legs_on_decent: bool = True, retract_palens_on_decent: bool = True, use_rcs_on_landing: bool = False, use_parachute: bool = True, ) -> None: """Vertical landing Extended description of function. Args: conn: kRPC connection auto_stage: staging when no fuel left on the stage stop_stage: stop staging on the stage deploy_legs_on_decent: extend legs on landing retract_palens_on_decent: retract panels on landing use_rcs_on_landing: use rcs or not use_parachute: use parachute Returns: return nothing, return when procedure finished """ vessel = conn.space_center.active_vessel body = vessel.orbit.body # check retrograde and radial hold capability use_sas = False try: vessel.control.sas = True vessel.control.sas_mode = vessel.control.sas_mode.retrograde vessel.control.sas_mode = vessel.control.sas_mode.radial use_sas = True except Exception: pass vessel.control.sas = False # Set up dialog and stream dialog = StatusDialog(conn) surface_gravity = body.surface_gravity equatorial_radius = body.equatorial_radius has_atmosphere = body.has_atmosphere atmosphere_depth = body.atmosphere_depth ref_frame = conn.space_center.ReferenceFrame.create_hybrid( position=body.reference_frame, rotation=vessel.surface_reference_frame) flight = vessel.flight(ref_frame) ut = conn.add_stream(getattr, conn.space_center, "ut") mass = conn.add_stream(getattr, vessel, "mass") available_thrust = conn.add_stream(getattr, vessel, "available_thrust") radius = conn.add_stream(getattr, vessel.orbit, "radius") altitude = conn.add_stream(getattr, flight, "surface_altitude") mean_altitude = conn.add_stream(getattr, flight, "mean_altitude") speed = conn.add_stream(getattr, flight, "speed") vertical_speed = conn.add_stream(getattr, flight, "vertical_speed") horizontal_speed = conn.add_stream(getattr, flight, "horizontal_speed") vessel.control.sas = True vessel.control.speed_mode = vessel.control.speed_mode.surface # set staging if auto_stage: set_autostaging(conn, stop_stage=stop_stage) # check unguided or guided guided_landing = True if target_lat is None or target_lon is None: guided_landing = False if not guided_landing: kill_horizontal_velocity(conn, use_sas) #### # pre-entry phase vessel.control.rcs = use_rcs_on_entry # pre-entry guidance if guided_landing: vessel.auto_pilot.reference_frame = ref_frame vessel.auto_pilot.engage() last_ut = ut() bearing, distance, landing_position_error = landing_target_steering( vessel, target_lat, target_lon, ut()) last_landing_position_error = landing_position_error last_throttle = 0 while True: a100 = available_thrust() / mass() bounding_box = vessel.bounding_box(vessel.surface_reference_frame) lower_bound = bounding_box[0][0] landing_radius = equatorial_radius + lower_bound if guided_landing: landing_radius = max( landing_radius, landing_radius + body.surface_height(target_lat, target_lon), ) bearing, distance, landing_position_error = landing_target_steering( vessel, target_lat, target_lon, ut()) if has_atmosphere: atmosphere_radius = equatorial_radius + atmosphere_depth if atmosphere_depth > altitude() and vertical_speed() < 0: break entry_ut, entry_speed = time_to_radius(vessel.orbit, atmosphere_radius, ut()) if entry_ut is None: break entry_lead_time = entry_ut - ut() if landing_position_error / distance < 0.05: if entry_lead_time > 120: conn.space_center.warp_to(entry_ut - 60) else: break else: impact_ut, terminal_speed = time_to_radius( vessel.orbit, landing_radius, ut()) burn_time = burn_prediction(terminal_speed, a100) burn_ut = impact_ut - burn_time burn_lead_time = burn_ut - ut() if burn_lead_time < 30: break if landing_position_error / distance < 0.05: if burn_lead_time > 10: conn.space_center.warp_to(burn_ut - 60) else: break vessel.auto_pilot.target_pitch_and_heading(0, bearing) if vessel.auto_pilot.heading_error < 1: try: landing_pos_corrected = (last_landing_position_error - landing_position_error) dt = ut() - last_ut instant_rate_per_throttle = (landing_pos_corrected / dt / last_throttle) instant_rate_per_throttle = max(1.0, instant_rate_per_throttle) vessel.control.throttle = min( 1.0, max( 0.05, landing_position_error / instant_rate_per_throttle, ), ) except Exception: vessel.control.throttle = 0.05 else: vessel.control.throttle = 0 dialog.status_update( f"landing_position error: {landing_position_error: 5.3f}, bearing: {bearing: 5.3f}" ) last_ut = ut() last_landing_position_error = landing_position_error last_throttle = vessel.control.throttle vessel.control.throttle = 0 vessel.auto_pilot.disengage() #### # entry vessel.control.sas = True vessel.control.sas_mode = vessel.control.sas_mode.retrograde # on entry: deploy leg, retract panel if deploy_legs_on_entry: deploy_legs(conn) if retract_palens_on_entry: retract_panels(conn) # wait for entry if has_atmosphere and atmosphere_depth < altitude(): warp_to_radius = atmosphere_depth + equatorial_radius entry_ut, terminal_speed = time_to_radius(vessel.orbit, warp_to_radius, ut()) sec_until_entry = entry_ut - ut() if sec_until_entry > 30: dialog.status_update( f"Warp for entry - 5sec: {sec_until_entry: 5.3f}") conn.space_center.warp_to(entry_ut - 5) time.sleep(5) #### # landing phase vessel.control.rcs = use_rcs_on_landing # warp for burn last_ut = ut() while True: a100 = available_thrust() / mass() lower_bound = vessel.bounding_box(vessel.surface_reference_frame)[0][0] landing_radius = equatorial_radius + lower_bound landing_altitude = altitude() + lower_bound if guided_landing: landing_radius = max( landing_radius, landing_radius + body.surface_height(target_lat, target_lon), ) landing_altitude = max( landing_altitude, landing_altitude + body.surface_height(target_lat, target_lon), ) impact_ut, terminal_speed = impact_prediction( radius(), landing_altitude, vertical_speed(), horizontal_speed(), surface_gravity, ut(), ) burn_time = burn_prediction(terminal_speed, a100) burn_lead_time = impact_ut - burn_time - ut() if burn_lead_time and burn_lead_time > (ut() - last_ut) * 1.5 + 2: if not has_atmosphere and burn_lead_time > 30: dialog.status_update( f"Warp for decereration burn - 30sec: {burn_lead_time: 5.3f}" ) conn.space_center.warp_to(ut() + burn_lead_time - 30) time.sleep(5) else: dialog.status_update( f"Wait for decereration burn: {burn_lead_time: 5.3f} sec; ut: {ut(): 5.3f}" ) else: break last_ut = ut() time.sleep(0.1) # on decent: deploy leg, retract panel if deploy_legs_on_decent: deploy_legs(conn) if retract_palens_on_decent: retract_panels(conn) if not guided_landing: # kill horizontal velocity again kill_horizontal_velocity(conn, use_sas) # Main decent loop last_sas_mode = vessel.control.sas_mode while True: a100 = available_thrust() / mass() bounding_box = vessel.bounding_box(vessel.surface_reference_frame) lower_bound = bounding_box[0][0] landing_radius = mean_altitude() + lower_bound landing_altitude = altitude() + lower_bound impact_ut, terminal_speed = impact_prediction( radius(), landing_altitude, vertical_speed(), horizontal_speed(), surface_gravity, ut(), ) burn_time = burn_prediction(terminal_speed, a100) burn_lead_time = impact_ut - burn_time - ut() dialog.status_update( f"Alt: {altitude(): 5.3f}, Speed {speed(): 5.3f} m/s (H: {horizontal_speed(): 5.3f}, V: {vertical_speed(): 5.3f}), " f"a: {a100: 5.3f}, g: {surface_gravity: 5.3f}, " f"landing in: {impact_ut - ut(): 5.3f} sec, burn lead time: {burn_lead_time: 5.3f} sec" ) if use_sas: if horizontal_speed() > 0.5 and speed() > 1.0: if last_sas_mode != vessel.control.sas_mode.retrograde: vessel.control.sas_mode = vessel.control.sas_mode.retrograde last_sas_mode = vessel.control.sas_mode.retrograde elif last_sas_mode != vessel.control.sas_mode.radial: vessel.control.sas_mode = vessel.control.sas_mode.radial last_sas_mode = vessel.control.sas_mode.radial else: # TODO: auto-pilot pass throttle = max( 0, min( 1.0, (-vertical_speed() + surface_gravity - landing_speed) / a100, ), ) vessel.control.throttle = throttle if is_grounded(vessel): vessel.control.sas_mode.radial vessel.control.throttle = 0 break last_ut = ut() dialog.status_update("Landed") # keep sas on for a bit to maintain landing stability time.sleep(5) if use_sas: vessel.control.sas = False else: vessel.auto_pilot.disengage() if auto_stage: unset_autostaging() return
def launch_into_orbit( conn: Client, target_alt: float, target_inc: float, turn_start_alt: float = 250, turn_end_alt: float = 45000, auto_launch: bool = True, auto_stage: bool = True, stop_stage: int = 0, exit_atm_stage: int = None, pre_circulization_stage: int = None, post_circulization_stage: int = None, skip_circulization: bool = False, use_rcs_on_ascent: bool = False, use_rcs_on_circulization: bool = False, deploy_panel_atm_exit: bool = True, deploy_panel_stage: int = None, ) -> None: """Lunch active vessel into orbit. Extended description of function. Args: conn: kRPC connection target_alt: target altitude in m target_inc: target inclination in degree auto_launch: when everything set, launch automatically auto_stage: staging when no fuel left on the stage stop_stage: stop staging on the stage exit_atm_stage: stage to this on atm exit pre_circulization_stage: stage to this before circulization post_circulization_stage: stage to this after circulization skip_circulization: skip circulization after ascent use_rcs_on_ascent: turn on rcs during ascent use_rcs_on_circulization: turn on rcs during circulization deploy_panel_atm_exit: deploy solar/radiator panels after atm exit deploy_panel_stage: deploy solar/radiator panels delayed on stage Returns: return nothing, return when procedure finished """ vessel = conn.space_center.active_vessel body = vessel.orbit.body # Set up dialog dialog = StatusDialog(conn) # Set up streams for telemetry ut = conn.add_stream(getattr, conn.space_center, "ut") atomosphere_depth = body.atmosphere_depth altitude = conn.add_stream(getattr, vessel.flight(), "mean_altitude") apoapsis = conn.add_stream(getattr, vessel.orbit, "apoapsis_altitude") # Pre-launch setup vessel.control.sas = True vessel.control.rcs = use_rcs_on_ascent vessel.control.throttle = 1.0 if vessel.situation.name == "pre_launch": if auto_launch: for i in range(-5, 0): dialog.status_update(f"T={i} ...") time.sleep(1) dialog.status_update("T=0; Lift off!!") vessel.control.activate_next_stage() else: dialog.status_update("Ready to launch") # Main ascent loop set_ascent_autostaging( conn, auto_stage=auto_stage, stop_stage=stop_stage, exit_atm_stage=exit_atm_stage, pre_circulization_stage=pre_circulization_stage, post_circulization_stage=post_circulization_stage, skip_circulization=skip_circulization, ) ascent_heading = (90 - target_inc) % 360 vessel.auto_pilot.engage() vessel.auto_pilot.target_pitch_and_heading(90, ascent_heading) turn_angle = 0 raise_apoapsis_last_throttle = vessel.control.throttle raise_apoapsis_last_apoapsis = apoapsis() raise_apoapsis_last_ut = ut() state_gravity_turn = False state_approach_target_ap = False state_coasting_out_of_atm = False while True: if apoapsis() <= target_alt * 0.9: vessel.control.throttle = 1 # Gravity turn if altitude() > turn_start_alt and altitude() < turn_end_alt: if not state_gravity_turn: dialog.status_update("Gravity turn") state_gravity_turn = True frac = (altitude() - turn_start_alt) / (turn_end_alt - turn_start_alt) new_turn_angle = frac * 90 if abs(new_turn_angle - turn_angle) > 0.5: turn_angle = new_turn_angle vessel.auto_pilot.target_pitch_and_heading( 90 - turn_angle, ascent_heading) # Decrease throttle when approaching target apoapsis elif apoapsis() < target_alt: if not state_approach_target_ap: dialog.status_update("Approaching target apoapsis") state_approach_target_ap = True vessel.auto_pilot.target_pitch_and_heading(0, ascent_heading) try: instant_rate_per_throttle = ( apoapsis() - raise_apoapsis_last_apoapsis) / ( (ut() - raise_apoapsis_last_ut) * raise_apoapsis_last_throttle) instant_rate_per_throttle = max(1.0, instant_rate_per_throttle) required_appoapsis_height = target_alt - apoapsis() vessel.control.throttle = min( 1, max( 0.05, required_appoapsis_height / instant_rate_per_throttle, ), ) except Exception: vessel.control.throttle = 0.05 # target appoapsis reached else: vessel.control.throttle = 0 if altitude() < atomosphere_depth: if not state_coasting_out_of_atm: dialog.status_update("Coasting out of atmosphere") state_coasting_out_of_atm = True else: break # wait an execute for exit_atm_stage, then re-set autostaging current_stage = vessel.control.current_stage if exit_atm_stage is not None: if (exit_atm_stage < current_stage and altitude() >= atomosphere_depth): dialog.status_update(f"Staging: stage {current_stage - 1}") vessel.control.activate_next_stage() if exit_atm_stage == current_stage - 1: set_ascent_autostaging( conn, auto_stage=auto_stage, stop_stage=stop_stage, pre_circulization_stage=pre_circulization_stage, post_circulization_stage=post_circulization_stage, skip_circulization=skip_circulization, ) raise_apoapsis_last_throttle = vessel.control.throttle raise_apoapsis_last_apoapsis = apoapsis() raise_apoapsis_last_ut = ut() vessel.control.throttle = 0 if auto_stage: unset_autostaging() if deploy_panel_atm_exit: deploy_panels(conn) if skip_circulization: return # pre-circularization setup vessel.control.rcs = use_rcs_on_circulization # Plan circularization burn (using vis-viva equation) dialog.status_update("Planning circularization burn") mu = vessel.orbit.body.gravitational_parameter r = vessel.orbit.apoapsis a1 = vessel.orbit.semi_major_axis a2 = r v1 = math.sqrt(mu * ((2.0 / r) - (1.0 / a1))) v2 = math.sqrt(mu * ((2.0 / r) - (1.0 / a2))) delta_v = v2 - v1 vessel.control.add_node(ut() + vessel.orbit.time_to_apoapsis, prograde=delta_v) vessel.auto_pilot.disengage() if pre_circulization_stage: while vessel.control.current_stage <= pre_circulization_stage: vessel.control.activate_next_stage() execute_next_node(conn, auto_stage=auto_stage) if post_circulization_stage: while vessel.control.current_stage <= post_circulization_stage: vessel.control.activate_next_stage() dialog.status_update("Launch complete") return
def set_autostaging( conn: Client, liquid_fuel: bool = True, oxidizer: bool = True, solid_fuel: bool = True, threashold: float = 0, stop_stage: int = 0, ) -> None: """set next autostagin on resource is under certin level Extended description of function. Args: conn: kRPC connection Returns: return nothing, return when procedure finished """ # TODO: global without lock global is_autostaging is_autostaging = True dialog = StatusDialog(conn) vessel = conn.space_center.active_vessel current_stage = vessel.control.current_stage if current_stage <= stop_stage: return resources_in_next_decoupled_stage = vessel.resources_in_decouple_stage( current_stage - 1) # TODO: offset must take account unused (undrained) resource, not only SF, that could be LF and/or O also? # ...or, completely replace staging logic like: just check all engines in decoupled stage and stage when all active engine doens't have fuel # downside of this logic, is we cannot take fuel threashold other than 0, that make powered recovery impossible resource_types_for_stage = [] if liquid_fuel and resources_in_next_decoupled_stage.has_resource( "LiquidFuel"): resource_types_for_stage.append("LiquidFuel") if oxidizer and resources_in_next_decoupled_stage.has_resource("Oxidizer"): resource_types_for_stage.append("Oxidizer") if solid_fuel and resources_in_next_decoupled_stage.has_resource( "SolidFuel"): # check solid fuel only if there's active srbs decoupled next srbs_next_decoupled = [ e for e in vessel.parts.engines if e.part.resources.has_resource("SolidFuel") and e.part.decouple_stage == (current_stage - 1) ] if len([e for e in srbs_next_decoupled if e.active]) > 0: # calculate solid fuel offset, fuels in non active srbs decoupled next (separetron) solidfuel_unused_decoupled_in_next_stage = sum([ e.part.resources.amount("SolidFuel") for e in srbs_next_decoupled if not e.active ]) resource_types_for_stage.append("SolidFuel") if len(resource_types_for_stage) == 0: # if current stage has empty resource (fairing etc.) just stage vessel.control.activate_next_stage() set_autostaging(conn, liquid_fuel, oxidizer, solid_fuel, threashold, stop_stage) return expression = conn.krpc.Expression call_current_stage = conn.get_call(getattr, vessel.control, "current_stage") staging_condition = expression.not_equal( expression.call(call_current_stage), expression.constant_int(current_stage), ) for resource in resource_types_for_stage: resource_threashold = threashold if resource == "SolidFuel": resource_threashold += solidfuel_unused_decoupled_in_next_stage resource_amount_call = conn.get_call( resources_in_next_decoupled_stage.amount, resource) cond = expression.less_than_or_equal( conn.krpc.Expression.call(resource_amount_call), conn.krpc.Expression.constant_float(resource_threashold), ) staging_condition = expression.or_(staging_condition, cond) global staging_event staging_event = conn.krpc.add_event(staging_condition) def auto_staging(): global is_autostaging remove_staging_events() if not is_autostaging: return dialog.status_update(f"Staging: stage {current_stage - 1}") # check if stage triggered by somewhere else if current_stage == vessel.control.current_stage: vessel.control.activate_next_stage() set_autostaging(conn, liquid_fuel, oxidizer, solid_fuel, threashold, stop_stage) staging_event.add_callback(auto_staging) staging_event.start()