def __init__(self, host = "localhost", service_port = 4000): """ Creates an instance of the MORSE simulator proxy. This is the main object you need to instanciate to communicate with the simulator. :param host: the simulator host (default: localhost) :param port: the port of the simulator socket interface (default: 4000) """ self._services_in_queue = Queue() # where we put the service result to read from MORSE self._services_out_queue = Queue() # where we put the request to send to MORSE self.com = ComChannel(host) # First, connect to the service port self.com.connect(service_port, self._services_in_queue, self._services_out_queue) self.executor = MorseExecutor(max_workers = 10, morse = self) self.initapi()
class Morse(): id = 0 def __init__(self, host = "localhost", service_port = 4000): """ Creates an instance of the MORSE simulator proxy. This is the main object you need to instanciate to communicate with the simulator. :param host: the simulator host (default: localhost) :param port: the port of the simulator socket interface (default: 4000) """ self._services_in_queue = Queue() # where we put the service result to read from MORSE self._services_out_queue = Queue() # where we put the request to send to MORSE self.com = ComChannel(host) # First, connect to the service port self.com.connect(service_port, self._services_in_queue, self._services_out_queue) self.executor = MorseExecutor(max_workers = 10, morse = self) self.initapi() def _normalize_name(self, name): """ Normalize Blender names to get valid Python identifiers """ new = name for c in ".-~": new = new.replace(c, "_") return new def initapi(self): """ This method asks MORSE for the scene structure, and dynamically creates corresponding objects in 'self'. """ simu = self.rpc("simulation", "details") self.robots = [] for r in simu["robots"]: name = self._normalize_name(r["name"]) self.robots.append(name) setattr(self, name, Robot()) robot = getattr(self, name) for c in sorted(r["components"].keys()): # important to sort the list of components to ensure parents are created before children self._add_component(robot, c, r["components"][c]) def _add_component(self, robot, fqn, details): stream = details.get('stream', None) if stream: port = self.get_stream_port(fqn) else: port = None services = details.get('services', []) name = fqn.split('.')[1:] # the first token is always the robot name. Remove it cmpt = Component(self, name[-1], fqn, stream, port, services) if len(name) == 1: # this component belongs to the robot directly. robot[name[0]] = cmpt else: subcmpt = robot[name[0]] for sub in name[1:-1]: subcmpt = getattr(subcmpt, sub) if hasattr(subcmpt, name[-1]): # pathologic cmpt name! raise RuntimeError("Sub-component name <%s> conflicts with <%s.%s> member. To use pymorse with this scenario, please change the name of the sub-component." % (name[-1], subcmpt.name, name[-1])) setattr(subcmpt, name[-1], cmpt) def cancel(self, id): """ Send a cancelation request for an existing (running) service. If the service is not running or does not exist, the request is ignored. """ req = {'id': id, 'special': 'cancel'} self._services_out_queue.put(req) self.com.process() def rpc(self, component, service, *args): """ Calls a service from the simulator. The call will block until a response from the simulator is received. :param component: the component that expose the service (like a robot name) :param service: the name of the service :param args...: (variadic) each service parameter, as a separate argument """ return self._execute_rpc(self._make_request(component, service, *args)) def _make_request(self, component, service, *args): req = {'id': str(self.id), 'component': component, 'service': service, 'args': args} self.id += 1 return req def _execute_rpc(self, req): self._services_out_queue.put(req) self.com.process() while True: res = self._services_in_queue.get() #block until we get an answer if res['id'] != req['id']: self._services_in_queue.put(res) time.sleep(0) # try to switch to another else: break if res['status'] == SUCCESS: if not res['result']: return None return res['result'] elif res['status'] == FAILURE: msg = res['result'] if msg and "wrong # of parameters" in msg: raise TypeError(msg) raise MorseServiceFailed(res['result']) elif res['status'] == PREEMPTED: msg = res['result'] raise MorseServicePreempted(res['result']) else: raise MorseServerError("Got an unexpected message status from MORSE: " + \ res['status']) def close(self, cancel_async_services = False): if cancel_async_services: pymorselogger.info('Cancelling all running asynchronous requests...') self.executor.cancel_all() else: pymorselogger.info('Waiting for all asynchronous requests to complete...') self.executor.shutdown(wait = True) self.com.close() pymorselogger.info('Done. Bye bye!') def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if not exc_type: self.close() else: self.close(True) return False # re-raise exception ##################################################################### ###### Predefined methods to interact with the simulator def quit(self): self.rpc("simulation", "quit") self.close() def reset(self): return self.rpc("simulation", "reset") def streams(self): return self.rpc("simulation", "list_streams") def get_stream_port(self, stream): return self.rpc("simulation", "get_stream_port", stream)