def post(): """Logic for POST events""" req_args = utils.parse_args(_PARSER) if not utils.sim_proxy().simulation.sector: return responses.bad_request_resp( "A sector definition is required before uploading a scenario") # TODO(rkm 2020-01-12) Should never include the file extension name = req_args["name"] if not name: return responses.bad_request_resp("Scenario name must be provided") content = req_args["content"] if content: err = validate_json_scenario(content) if err: return responses.bad_request_resp( f"Invalid scenario content: {err}") scenario = ScenarioWrapper(name, content) err = utils.sim_proxy().simulation.load_scenario(scenario) return responses.checked_resp(err, HTTPStatus.CREATED)
def get(): """Logic for GET events. Returns the current episode ID and log content""" req_args = utils.parse_args(_PARSER) close_ep = req_args.get("close_ep", False) if not in_agent_mode(): return responses.bad_request_resp( "Episode data only recorded when in Agent mode") ep_file_path = bb_logging.EP_FILE if not ep_file_path: return responses.bad_request_resp("No episode being recorded") if close_ep: err = utils.sim_proxy().simulation.reset() if err: return responses.internal_err_resp( f"Couldn't reset simulation: {err}") if not ep_file_path.exists(): return responses.internal_err_resp("Could not find episode file") lines = list(line.rstrip("\n") for line in open(ep_file_path)) data = { "cur_ep_id": bb_logging.EP_ID, "cur_ep_file": str(ep_file_path.absolute()), "log": lines, } return responses.ok_resp(data)
def post(): """Upload a new sector definition""" req_args = utils.parse_args(_PARSER) sector_name = req_args["name"] if not sector_name: return responses.bad_request_resp("Sector name must be provided") sector_json = req_args["content"] if sector_json: sector_element = validate_geojson_sector(sector_json) if not isinstance(sector_element, SectorElement): return responses.bad_request_resp( f"Invalid sector content: {sector_element}" ) else: sector_element = None sector = SectorWrapper(sector_name, sector_element) err = utils.sim_proxy().simulation.load_sector(sector) return responses.checked_resp(err, HTTPStatus.CREATED)
def get(): """ Logic for GET events. If the request contains an identifier to an existing aircraft, then information about its route (FMS flightplan) is returned """ req_args = utils.parse_args(_PARSER) callsign = req_args[utils.CALLSIGN_LABEL] resp = utils.check_exists(utils.sim_proxy(), callsign) if resp: return resp route_info = utils.sim_proxy().aircraft.route(callsign) if not isinstance(route_info, tuple): if route_info == "Aircraft has no route": return responses.bad_request_resp(route_info) return responses.internal_err_resp(route_info) data = { utils.CALLSIGN_LABEL: str(callsign), "route_name": route_info[0], "next_waypoint": route_info[1], "route_waypoints": route_info[2], } return responses.ok_resp(data)
def post(): """ Logic for POST events. If the request contains valid aircraft information, then a request is sent to the simulator to create it """ req_args = utils.parse_args(_PARSER) callsign = req_args[utils.CALLSIGN_LABEL] resp = utils.check_exists(utils.sim_proxy(), callsign, negate=True) if resp: return resp position_or_resp = utils.try_parse_lat_lon(req_args) if not isinstance(position_or_resp, LatLon): return position_or_resp if not req_args["type"]: return responses.bad_request_resp( "Aircraft type must be specified") err = utils.sim_proxy().aircraft.create( callsign, req_args["type"], position_or_resp, req_args["hdg"], req_args["alt"], req_args["gspd"], ) return responses.checked_resp(err, HTTPStatus.CREATED)
def post(): """Logic for POST events. Sets the seed of the simulator""" req_args = utils.parse_args(_PARSER) seed: int = req_args["value"] if not is_valid_seed(seed): return responses.bad_request_resp( "Invalid seed specified. Must be a positive integer less than 2^32" ) err = utils.sim_proxy().simulation.set_seed(seed) return responses.checked_resp(err)
def post(): """Logic for POST events. Sets the speed multiplier for the simulation""" req_args = utils.parse_args(_PARSER) multiplier = round(req_args["multiplier"], 2) if multiplier <= 0: return responses.bad_request_resp( "Multiplier must be greater than 0") # TODO Check if we still need to keep track of step_dt in the client err = utils.sim_proxy().simulation.set_speed(multiplier) return responses.checked_resp(err)
def post(): """ Logic for POST events. If the request contains an existing aircraft ID, then a request is sent to alter its altitude """ req_args = utils.parse_args(_PARSER_POST) callsign: Callsign = req_args[utils.CALLSIGN_LABEL] fl_cleared: Altitude = req_args["alt"] err = utils.sim_proxy().aircraft.set_cleared_fl( callsign, fl_cleared, vspd=req_args.get("vspd")) return responses.checked_resp(err)
def post(): """ Logic for POST events. If the request contains an existing aircraft ID, then a request is sent to alter its ground speed """ req_args = utils.parse_args(_PARSER) callsign = req_args[utils.CALLSIGN_LABEL] resp = utils.check_exists(utils.sim_proxy(), callsign) if resp: return resp err = utils.sim_proxy().aircraft.set_ground_speed(callsign, req_args["gspd"]) return checked_resp(err)
def post(): """ Logic for POST events. If the request contains an existing aircraft ID, then a request is sent to alter its heading """ req_args = utils.parse_args(_PARSER) callsign = req_args[utils.CALLSIGN_LABEL] resp = utils.check_exists(utils.sim_proxy(), callsign) if resp: return resp heading = req_args["hdg"] err = utils.sim_proxy().aircraft.set_heading(callsign, heading) return responses.checked_resp(err)
def get(): """Logic for GET events. Returns properties for the specified aircraft""" req_args = utils.parse_args(_PARSER) callsign = req_args[utils.CALLSIGN_LABEL] sim_props = utils.sim_proxy().simulation.properties if not isinstance(sim_props, SimProperties): return responses.internal_err_resp(sim_props) if callsign: resp = utils.check_exists(utils.sim_proxy(), callsign) if resp: return resp props = utils.sim_proxy().aircraft.properties(callsign) if not isinstance(props, AircraftProperties): return internal_err_resp(props) data = utils.convert_aircraft_props(props) data.update({"scenario_time": sim_props.scenario_time}) return responses.ok_resp(data) # else: get_all_properties props = utils.sim_proxy().aircraft.all_properties if isinstance(props, str): return responses.internal_err_resp( f"Couldn't get the aircraft properties: {props}") if not props: return responses.bad_request_resp("No aircraft in the simulation") data = {} for prop in props.values(): data.update(utils.convert_aircraft_props(prop)) data["scenario_time"] = sim_props.scenario_time return responses.ok_resp(data)
def post(): """Shuts down the BlueBird server""" req_args = utils.parse_args(_PARSER) sim_quit = utils.sim_proxy().shutdown( shutdown_sim=bool(req_args["stop_sim"])) sim_quit_msg = f"(Sim shutdown ok = {sim_quit})" # TODO Check we still get a response before this executes. If not, need to set # this to fire on a timer try: shutdown_fn = request.environ.get("werkzeug.server.shutdown") if not shutdown_fn: return responses.internal_err_resp( f"No shutdown function available. {sim_quit_msg}") shutdown_fn() except Exception as exc: return responses.internal_err_resp( f"Could not shutdown: {exc}. {sim_quit_msg}") data = f"BlueBird shutting down! {sim_quit_msg}" return responses.ok_resp(data)
def post(): """ Requests that the specified aircraft proceeds immediately to the specified waypoint """ req_args = utils.parse_args(_PARSER) waypoint_name = req_args["waypoint"] if not waypoint_name: return responses.bad_request_resp( "Waypoint name must be specified") callsign = req_args[utils.CALLSIGN_LABEL] resp = utils.check_exists(utils.sim_proxy(), callsign) if resp: return resp err = utils.sim_proxy().aircraft.direct_to_waypoint( callsign, waypoint_name) return responses.checked_resp(err)
def post(): """ Logic for POST events. Returns the simulator to a previous state given a logfile :return: """ if Settings.SIM_MODE != SimMode.Agent: return responses.bad_request_resp("Can only be used in agent mode") if Settings.SIM_TYPE != SimType.BlueSky: return responses.bad_request_resp( f"Method not supported for the {Settings.SIM_TYPE} simulator") req_args = parse_args(_PARSER) if bool(req_args["filename"]) == bool(req_args["lines"]): return responses.bad_request_resp( "Either filename or lines must be specified") target_time = req_args["time"] if target_time <= 0: return responses.bad_request_resp( "Target time must be greater than 0") props = sim_proxy().simulation.properties if isinstance(props, str): return responses.internal_err_resp( f"Could not get the sim properties: {props}") prev_dt = props.dt _LOGGER.debug("Starting log reload") # Reset now so the current episode log is closed err = sim_proxy().simulation.reset() if err: return responses.internal_err_resp(f"Simulation not reset: {err}") if req_args["filename"]: log_path = Path(os.getcwd(), req_args["filename"]) if not log_path.exists(): return responses.bad_request_resp( f'Could not find episode file {req_args["filename"]}') with open(req_args["filename"]) as log_file: lines = list(log_file) else: lines = req_args["lines"] _LOGGER.debug("Parsing log content") parsed_scn = parse_lines(lines, target_time) if isinstance(parsed_scn, str): return responses.bad_request_resp( f"Could not parse episode content: {parsed_scn}") # TODO Move regex to outer scope and comment # Assert that the requested time is not past the end of the log last_data = next(x for x in reversed(lines) if re.match(r".*A \[(\d+)\] (.*)$", x)) last_time = int(re.search(r"\[(.*)]", last_data).group(1)) if target_time > last_time: return responses.bad_request_resp( "Error: Target time was greater than the latest time in the log" ) # err = validate_scenario(parsed_scn["lines"]) if err: return responses.bad_request_resp( "Could not create a valid scenario from the given log") # All good - do the reload _LOGGER.debug("Setting the simulator seed") err = sim_proxy().simulation.set_seed(int(parsed_scn["seed"])) if err: return responses.internal_err_resp(f"Could not set seed {err}") scn_name = f"reloads/{str(uuid.uuid4())[:8]}.scn" _LOGGER.debug("Uploading the new scenario") # store_local_scn(scn_name, parsed_scn["lines"]) err = sim_proxy().simulation.upload_new_scenario( scn_name, parsed_scn["lines"]) if err: return responses.internal_err_resp( f"Error uploading scenario: {err}") _LOGGER.info("Starting the new scenario") err = sim_proxy().simulation.load_scenario(scn_name, start_paused=True) if err: return responses.internal_err_resp( f"Could not start scenario after upload {err}") props = sim_proxy().simulation.properties if isinstance(props, str): return responses.internal_err_resp( f"Could not get the sim properties: {props}") diff = target_time - props.scenario_time if diff: # Naive approach - set DTMULT to target, then STEP once... _LOGGER.debug( f"Time difference is {diff}. Stepping to {target_time}") err = sim_proxy().simulation.set_speed(diff) if err: return responses.internal_err_resp( f"Could not change speed: {err}") _LOGGER.debug("Performing step") err = sim_proxy().simulation.step() if err: return responses.internal_err_resp( f"Could not step simulations: {err}") else: _LOGGER.debug("Simulation already at required time") # Reset DTMULT to the previous value err = sim_proxy().simulation.set_speed(prev_dt) if err: return responses.internal_err_resp( "Episode reloaded, but could not reset DTMULT to previous value" ) # TODO Do we want to check before/after positions here and check if the # differences are acceptable? return responses.ok_resp()
def get(): utils.parse_args(parser)