async def scan(self, scanning_radius_km: int, minimal_radius_m: int, result_cb: Callable[[ObjectsList, Error], None], timeout: float) -> Optional[str]: """Scanning all bodies within a 'scanning_radius_km' radius. Bodies with radius less then the specified 'minimal_radius_m' will be ignored. The 'result_cb' callback will be called when another portion of data received from the server. After all data received callback will be called for the last time with (None, None) argument. Return None on success otherwise return error string """ request = api.Message() scan_req = request.celestial_scanner.scan scan_req.scanning_radius_km = scanning_radius_km scan_req.minimal_radius_m = minimal_radius_m if not self.send(message=request): error = "Failed to send request" result_cb(None, error) return error continue_scanning = True while continue_scanning: response, _ = await self.wait_message(timeout=timeout) body = api.get_message_field(response, ["celestial_scanner"]) if not body: error = "No response" result_cb(None, error) return error report = api.get_message_field(body, ["scanning_report"]) if not report: fail = api.get_message_field(body, ["scanning_failed"]) if fail: error = CelestialScannerI.Status.convert(fail).value else: error = self.__unexpected_msg_str(fail) result_cb(None, error) return error timestamp: Optional[int] = response.timestamp or None result_cb([ self.__build_object(body, timestamp) for body in report.asteroids ], None) continue_scanning = report.left > 0 result_cb(None, None) # Scanning is finished return None
async def wait_state(self, timeout: float = 0.5) -> Optional[State]: """Await a State message with actual ship's state""" message, timestamp = await self.wait_message(timeout) if not message: return None state = api.get_message_field(message, ["ship", "state"]) return State.build(state, timestamp) if state else None
async def wait_timestamp(self, timeout: float = 0.5) -> Optional[int]: """Wait for a 'time' message, that carries current system clock's time""" response, _ = await self.wait_message(timeout=timeout) if not response: self.logger.warning("Timeout while waiting timestamp") return None return api.get_message_field(response, ["system_clock", "time"])
async def _wait_start_mining_status(self, timeout: float = 1) -> Status: response, _ = await self.wait_message(timeout=timeout) if not response: return AsteroidMinerI.Status.RESPONSE_TIMEOUT mining_status = api.get_message_field( response, ["asteroid_miner", "start_mining_status"]) if mining_status is None: return AsteroidMinerI.Status.UNEXPECTED_RESPONSE return AsteroidMinerI.Status.from_protobuf(mining_status)
async def wait_building_report(self, timeout: float = 1.0) -> \ (Status, Optional[float]): response, _ = await self.wait_message(timeout=timeout) if not response: return ShipyardI.Status.RESPONSE_TIMEOUT, None report = api.get_message_field(response, ["shipyard", "building_report"]) if not report: return ShipyardI.Status.UNEXPECTED_RESPONSE, None return ShipyardI.Status.from_protobuf(report.status), report.progress
async def transfer(self, port: int, access_key: int, resource: ResourceItem, progress_cb: Optional[Callable[[ResourceItem], None]] = None, timeout: int = 0.5) -> Status: """Transfer the specified 'resource' to the specified 'port' with the specified 'access_key'. The optionally specified 'progress_cb' will be called to report transferring status (a total amount of transferred resources).""" request = api.Message() req_body = request.resource_container.transfer req_body.port_id = port req_body.access_key = access_key resource_item = req_body.resource resource.to_protobuf(resource_item) if not self.send(message=request): return ResourceContainerI.Status.FAILED_TO_SEND_REQUEST response, _ = await self.wait_message(timeout=timeout) if not response: return ResourceContainerI.Status.RESPONSE_TIMEOUT status = api.get_message_field( response, ["resource_container", "transfer_status"]) if status is None: return ResourceContainerI.Status.UNEXPECTED_RESPONSE if status != api.IResourceContainer.Status.SUCCESS: return ResourceContainerI.Status.convert(status) # Status is success. Waiting for reports while True: response, _ = await self.wait_message(timeout=2) report = api.get_message_field( response, ["resource_container", "transfer_report"]) if not report: # May be complete status is received: status = api.get_message_field( response, ["resource_container", "transfer_finished"]) return ResourceContainerI.Status.convert(status) \ if status is not None else ResourceContainerI.Status.UNEXPECTED_RESPONSE # Got transfer report: if progress_cb is not None: progress_cb(ResourceItem.from_protobuf(report))
async def wait_until(self, time: int, timeout: float) -> Optional[int]: """Wait until server time reaches the specified 'time' Return actual server's time""" request = api.Message() request.system_clock.wait_until = time if not self.send(message=request): return None response, _ = await self.wait_message(timeout=timeout) if not response: return None return api.get_message_field(response, ["system_clock", "ring"])
async def get_blueprint(self, blueprint_name: str, timeout: float = 0.5) \ -> (Status, Optional[Blueprint]): request = api.Message() request.blueprints_library.blueprint_req = blueprint_name if not self.send(request): return Status.FAILED_TO_SEND_REQUEST, None response, _ = await self.wait_message(timeout=timeout) if not response: return Status.RESPONSE_TIMEOUT, None blueprint = api.get_message_field(response, ["blueprints_library", "blueprint"]) if blueprint: return Status.SUCCESS, Blueprint.from_protobuf(blueprint) fail = api.get_message_field(response, ["blueprints_library", "blueprint_fail"]) if fail: return Status.from_protobuf(fail), None
async def _wait_mining_report(self, timeout: float) \ -> (Status, Optional[ResourcesDict]): response, _ = await self.wait_message(timeout=timeout) if not response: return AsteroidMinerI.Status.RESPONSE_TIMEOUT, None report = api.get_message_field(response, ["asteroid_miner", "mining_report"]) if report is not None: resources = { ResourceType.from_protobuf(item.type): item.amount for item in report.items } return AsteroidMinerI.Status.SUCCESS, resources # May be mining was interrupted, that is why we haven't received the report? stop_ind = api.get_message_field( response, ["asteroid_miner", "mining_is_stopped"]) if stop_ind is not None: return AsteroidMinerI.Status.from_protobuf(stop_ind), None return AsteroidMinerI.Status.UNEXPECTED_RESPONSE, None
async def wait_for(self, period_us: int, timeout: float) -> Optional[int]: """Wait for the specified 'period' microseconds Return actual server's time""" request = api.Message() request.system_clock.wait_for = period_us if not self.send(message=request): return None response, _ = await self.wait_message(timeout=timeout) if not response: return None return api.get_message_field(response, ["system_clock", "ring"])
async def wait_building_complete(self, timeout: float = 1.0) -> \ (Status, Optional[str], Optional[int]): response, _ = await self.wait_message(timeout=timeout) if not response: return ShipyardI.Status.RESPONSE_TIMEOUT, None, None report = api.get_message_field(response, ["shipyard", "building_complete"]) if not report: return ShipyardI.Status.UNEXPECTED_RESPONSE, None, None return ShipyardI.Status.SUCCESS, \ report.ship_name, \ report.slot_id
async def start_monitoring(self, timeout: float = 0.5) -> Status: request = api.Message() request.passive_scanner.monitor = True if not self.send(message=request): return PassiveScannerI.Status.FAILED_TO_SEND_REQUEST response, _ = await self.wait_message(timeout=timeout) if not response: return PassiveScannerI.Status.RESPONSE_TIMEOUT ack = api.get_message_field(response, ["passive_scanner", "monitor_ack"]) return PassiveScannerI.Status.SUCCESS \ if ack else PassiveScannerI.Status.MONITORING_FAILED
async def get_thrust(self, timeout: float = 0.5) -> Optional[Vector]: """Return current engine thrust""" request = api.Message() request.engine.thrust_req = True if not self.send(message=request): return None response, _ = await self.wait_message(timeout=timeout) if not response: return None thrust = api.get_message_field(response, ["engine", "thrust"]) if not thrust: return None return Vector(x=thrust.x, y=thrust.y).set_length(thrust.thrust)
async def wait_message(self, timeout: float = 1.0) -> \ Tuple[Optional[Any], Optional[int]]: """Await for a message on the internal queue for not more than the specified 'timeout' seconds. Return a message and an optional timestamp, when the message was sent.""" try: message, timestamp = \ await asyncio.wait_for(self.queue.get(), timeout=timeout) if get_message_field(message, ["session", "closed_ind"]): raise ChannelClosed() return message, timestamp except asyncio.TimeoutError: return None, None
async def get_specification(self, timeout: float = 0.5)\ -> Optional[Specification]: request = api.Message() request.passive_scanner.specification_req = True if not self.send(message=request): return None response, _ = await self.wait_message(timeout=timeout) if not response: return None spec = api.get_message_field(response, ["passive_scanner", "specification"]) if not spec: return None return Specification(scanning_radius_km=spec.scanning_radius_km, max_update_time_ms=spec.max_update_time_ms)
async def wait_exact(self, message: List[str], timeout: float = 1.0) \ -> Tuple[Optional[Any], Optional[int]]: """Await for the specified 'message' but not more than 'timeout' seconds. Ignore all other received messages. Return expected message and timestamp or None""" while timeout > 0: start_at = time.monotonic() received_msg, timestemp = await self.wait_message(timeout) expected_msg = get_message_field(received_msg, message) if expected_msg is not None: return expected_msg, timestemp # Got unexpected message. Just ignoring it timeout -= time.monotonic() - start_at return None, None
async def close_port(self, timeout: int = 0.5) -> Status: """Close an existing opened port""" request = api.Message() request.resource_container.close_port = True if not self.send(message=request): return ResourceContainerI.Status.FAILED_TO_SEND_REQUEST response, _ = await self.wait_message(timeout=timeout) if not response: return ResourceContainerI.Status.RESPONSE_TIMEOUT status = api.get_message_field( response, ["resource_container", "close_port_status"]) if status is not None: # Success case return ResourceContainerI.Status.convert(status)
async def open_port(self, access_key: int, timeout: int = 0.5) -> (Status, int): """Open a new port with the specified 'access_key'. Return operation status and port number""" request = api.Message() request.resource_container.open_port = access_key if not self.send(message=request): return ResourceContainerI.Status.FAILED_TO_SEND_REQUEST, 0 response, _ = await self.wait_message(timeout=timeout) if not response: return ResourceContainerI.Status.RESPONSE_TIMEOUT, 0 port_id = api.get_message_field(response, ["resource_container", "port_opened"]) if port_id is not None: # Success case return ResourceContainerI.Status.SUCCESS, port_id error_status = api.get_message_field( response, ["resource_container", "open_port_failed"]) if error_status is not None: return ResourceContainerI.Status.convert(error_status), 0 return ResourceContainerI.Status.UNEXPECTED_RESPONSE, 0
async def get_specification(self, timeout: float = 0.5) \ -> (Status, Optional[Specification]): request = api.Message() request.shipyard.specification_req = True if not self.send(message=request): return ShipyardI.Status.FAILED_TO_SEND_REQUEST, None response, _ = await self.wait_message(timeout=timeout) if not response: return ShipyardI.Status.RESPONSE_TIMEOUT, None spec = api.get_message_field(response, ["shipyard", "specification"]) if not spec: return ShipyardI.Status.UNEXPECTED_RESPONSE, None spec = Specification(labor_per_sec=spec.labor_per_sec) return ShipyardI.Status.SUCCESS, spec
async def bind_to_cargo(self, cargo_name: str, timeout: float = 0.5) -> Status: request = api.Message() request.shipyard.bind_to_cargo = cargo_name if not self.send(message=request): return ShipyardI.Status.FAILED_TO_SEND_REQUEST response, _ = await self.wait_message(timeout=timeout) if not response: return ShipyardI.Status.RESPONSE_TIMEOUT status = api.get_message_field(response, ["shipyard", "bind_to_cargo_status"]) if status is None: return ShipyardI.Status.UNEXPECTED_RESPONSE return ShipyardI.Status.from_protobuf(status)
async def get_specification(self, timeout: float = 0.5)\ -> Optional[Specification]: request = api.Message() request.celestial_scanner.specification_req = True if not self.send(message=request): return None response, _ = await self.wait_message(timeout=timeout) if not response: return None spec = api.get_message_field(response, ["celestial_scanner", "specification"]) if not spec: return None return Specification(max_radius_km=spec.max_radius_km, processing_time_us=spec.processing_time_us)
async def set_position(self, center_x: float, center_y: float, width: float, height: float): """Move screen with the specified 'width' and 'height' to the specified 'x' and 'y' position""" message = api.admin.Message() position = message.screen.move position.x = center_x position.y = center_y position.width = width position.height = height self.send(message) response = await self.wait_message() status = api.get_message_field(response, ["screen", "status"]) return status is not None and status == api.admin.Screen.Status.SUCCESS
async def stop_mining(self, timeout: float = 0.5) -> Status: """Stop the mining process""" request = api.Message() request.asteroid_miner.stop_mining = True if not self.send(message=request): return AsteroidMinerI.Status.FAILED_TO_SEND_REQUEST response, _ = await self.wait_message(timeout=timeout) if not response: return AsteroidMinerI.Status.RESPONSE_TIMEOUT status = api.get_message_field( response, ["asteroid_miner", "stop_mining_status"]) if status is None: return AsteroidMinerI.Status.UNEXPECTED_RESPONSE return AsteroidMinerI.Status.from_protobuf(status)
async def get_position(self, timeout: float = 0.5) -> Optional[Position]: """Request current ship's position. Will block until the response is received or the specified 'timeout' occurs.""" request = api.Message() request.navigation.position_req = True if not self.send(message=request): return None response, _ = await self.wait_message(timeout=timeout) if not response: return None position = api.get_message_field(response, ["navigation", "position"]) if not position: return None assert response.timestamp is not None return Position.from_protobuf(position, timestamp=response.timestamp)
async def bind_to_cargo(self, cargo_name: str, timeout: float = 0.5) -> Status: """Bind miner to the container with the specified 'cargo_name'""" request = api.Message() request.asteroid_miner.bind_to_cargo = cargo_name if not self.send(message=request): return AsteroidMinerI.Status.FAILED_TO_SEND_REQUEST response, _ = await self.wait_message(timeout=timeout) if not response: return AsteroidMinerI.Status.RESPONSE_TIMEOUT protobuf_status = api.get_message_field( response, ["asteroid_miner", "bind_to_cargo_status"]) if protobuf_status is None: return AsteroidMinerI.Status.UNEXPECTED_RESPONSE return AsteroidMinerI.Status.from_protobuf(protobuf_status)
async def show(self, object_type: ObjectType) -> List[PhysicalObject]: message = api.admin.Message() message.screen.show = object_type.to_protobuf_type() self.send(message) shown_objects: List[PhysicalObject] = [] while True: response = await self.wait_message() items = api.get_message_field(response, ["screen", "objects"]) if not items: break for item in items.object: shown_objects.append(PhysicalObject().from_protobuf(item)) if items.left == 0: break return shown_objects
async def wait_content(self, timeout: float = 0.5) -> \ Tuple[Status, Optional[Content]]: response, timestamp = await self.wait_message(timeout=timeout) if not response: return ResourceContainerI.Status.FAILED_TO_SEND_REQUEST, None content = api.get_message_field(response, ["resource_container", "content"]) if not content: return ResourceContainerI.Status.RESPONSE_TIMEOUT, None content = ResourceContainerI.Content(timestamp=timestamp, volume=content.volume, used=content.used, resources={ ResourceType.from_protobuf( item.type): item.amount for item in content.resources }) return ResourceContainerI.Status.SUCCESS, content
async def get_specification(self, timeout: float = 0.5)\ -> (Status, Optional[Specification]): status = AsteroidMinerI.Status request = api.Message() request.asteroid_miner.specification_req = True if not self.send(message=request): return status.FAILED_TO_SEND_REQUEST, None response, _ = await self.wait_message(timeout=timeout) if not response: return status.RESPONSE_TIMEOUT, None spec = api.get_message_field(response, ["asteroid_miner", "specification"]) if not spec: return status.UNEXPECTED_RESPONSE, None spec = Specification(max_distance=spec.max_distance, cycle_time_ms=spec.cycle_time_ms, yield_per_cycle=spec.yield_per_cycle) return status.SUCCESS, spec
async def get_specification(self, timeout: float = 0.5, reset_cached=False)\ -> Optional[Specification]: if reset_cached: self.specification = None if self.specification: return self.specification request = api.Message() request.engine.specification_req = True if not self.send(message=request): return None response, _ = await self.wait_message(timeout=timeout) if not response: return None spec = api.get_message_field(response, ["engine", "specification"]) if not spec: return None self.specification = Specification(max_thrust=spec.max_thrust) return self.specification
async def get_blueprints_list(self, start_with: str = "", timeout: float = 0.5) -> (Status, List[str]): request = api.Message() request.blueprints_library.blueprints_list_req = start_with if not self.send(request): return Status.FAILED_TO_SEND_REQUEST, [] names: List[str] = [] done = False while not done: response, _ = await self.wait_message(timeout=timeout) if not response: return Status.RESPONSE_TIMEOUT, [] response = api.get_message_field( response, ["blueprints_library", "blueprints_list"]) if not response: return Status.UNEXPECTED_RESPONSE, [] names.extend(response.names) done = response.left == 0 return Status.SUCCESS, names