class EngagementHandler(object): def __init__(self, session): self.logger = logging.getLogger("EngagementHandler") self.session = session self.last_time_detected = 0 # log the time self.notification_interval = 1 # seconds self.detected_faces_lst = [] # observers self.face_detected_observers = Observable() # subscribe to face events self.session.subscribe(self.on_face_detected, "rom.optional.face.stream") @inlineCallbacks def on_face_detected(self, frame): self.logger.info("Face detected: {}".format(frame)) try: if frame is None or len(frame) == 0: yield sleep(1) else: detected_face = frame["data"]["body"] # skip empty frames if detected_face and len(detected_face) > 0: # check face size face_size = frame["data"]["body"][0][2] self.detected_faces_lst.append(face_size) # > x seconds: notify observers detection_interval = time.time() - self.last_time_detected if detection_interval >= self.notification_interval: self.logger.info("Detected a face: {} | after {}s".format(frame["data"], detection_interval)) self.last_time_detected = time.time() face_size = max(self.detected_faces_lst) self.detected_faces_lst = [] self.face_detected_observers.notify_all(face_size) yield sleep(1) else: yield sleep(1) except Exception as e: self.logger.error("Error while receiving the detected face: {}".format(e)) yield sleep(1) def face_detection(self, start=False): # start/close the face stream self.session.call("rom.optional.face.stream" if start else "rom.optional.face.close") def face_tracker(self, start=False): self.session.call("rom.optional.face.face_tracker", start=start)
class ConnectionHandler(object): def __init__(self): self.logger = logging.getLogger("Connection Handler") self.rie = None self.session_observers = Observable() self.session = None @inlineCallbacks def on_connect(self, session, details=None): self.logger.debug("Created session: {}".format(session)) self.session = session yield self.session_observers.notify_all(session) def start_rie_session(self, robot_name=None, robot_realm=None): try: if robot_realm is None: # get the realm from config name_key = "pepper" if robot_name is None else robot_name.lower( ) robot_realm = config_helper.get_robot_settings( )["realm"][name_key] self.logger.info("{} REALM: {}".format(robot_name, robot_realm)) self.rie = Component(transports=[{ 'url': u"wss://wamp.robotsindeklas.nl", 'serializers': ['msgpack'], 'max_retries': 0 }], realm=robot_realm) self.logger.info("** {}".format(threading.current_thread().name)) self.rie.on_join(self.on_connect) self.logger.info("Running the rie component") run([self.rie]) except Exception as e: self.logger.error("Unable to run the rie component | {}".format(e)) def stop_session(self): try: if self.session: self.session.leave() self.session_observers.notify_all(None) self.logger.info("Closed the robot session.") else: self.logger.info("There is no active session.") except Exception as e: self.logger.error("Error while closing rie session: {}".format(e))
class InteractionController(QtCore.QObject): face_detected_signal = Signal(float) def __init__(self, block_controller, music_controller=None): super().__init__() self.logger = logging.getLogger("Interaction Controller") self.block_controller = block_controller self.music_controller = music_controller self.db_stream_controller = DBStreamController() self.timer_helper = TimerHelper() self.music_command = None self.animations_lst = [] self.animation_time = 0 self.animation_counter = -1 self.robot_volume = 50 self.robot_name = None self.robot_realm = None self.current_interaction_block = None self.previous_interaction_block = None self.interaction_module = None self.comm_style = CommunicationStyle.UNDEFINED self.is_interacting = False self.face_size_dict = {} self.execution_result = None self.executed_blocks = [] self.tablet_input = None self.has_finished_playing_observers = Observable() self.threads = [] self.on_connected_observers = Observable() self.start_listening_to_db_stream() def connect_to_robot(self, robot_name=None, robot_realm=None, robot_ip=None, robot_port=None): self.robot_name = robot_name self.robot_realm = robot_realm self.db_stream_controller.update_one(self.db_stream_controller.interaction_collection, data_key="connectRobot", data_dict={"connectRobot": {"robotName": self.robot_name, "robotRealm": self.robot_realm, "robotIP": robot_ip, "robotPort": robot_port}, "timestamp": time.time()}) def on_connect(self, data_dict=None): try: self.logger.info("Connect data received: {}".format(data_dict)) if data_dict and data_dict["isConnected"] is True: self.logger.debug("Connected...") self.on_connected_observers.notify_all(True) else: self.logger.info("Robot is not connected!") except Exception as e: self.logger.error("Error while extracting isConnected: {} | {}".format(data_dict, e)) def disconnect(self): try: self.logger.info("Disconnecting...") self.engagement(start=False) self.db_stream_controller.update_one(self.db_stream_controller.interaction_collection, data_key="disconnectRobot", data_dict={"disconnectRobot": True, "timestamp": time.time()}) self.logger.info("Disconnection was successful.") except Exception as e: self.logger.error("Error while disconnecting: {}".format(e)) return True def reset(self): self.execution_result = "" self.tablet_input = "" self.current_interaction_block = None self.previous_interaction_block = None self.executed_blocks = [] def on_exit(self): self.logger.info("Exiting...") self.db_stream_controller.stop_db_stream() def stop_all_threads(self): # close all threads for thread in self.threads: try: thread.stop_running() thread.quit() thread.wait() except Exception as e: self.logger.error("Error while stopping thread: {} | {}".format(thread, e)) continue self.threads = [] def is_connected(self): success = False conn = None try: conn = self.db_stream_controller.find_one(self.db_stream_controller.robot_collection, "isConnected") if conn and conn["isConnected"] is True: success = True except Exception as e: self.logger.error("Error while extracting isConnected: {} | {}".format(conn, e)) finally: return success def start_listening_to_db_stream(self): observers_dict = { "isConnected": self.on_connect, "isExecuted": self.on_block_executed, "isDisengaged": self.on_disengaged, "startInteraction": self.on_start_interaction, "tabletInput": self.on_tablet_input, "faceDetected": self.on_face_detected } self.db_stream_controller.start_db_stream(observers_dict=observers_dict, db_collection=self.db_stream_controller.robot_collection, target_thread="qt") def on_block_executed(self, data_dict=None): self.execution_result = "" try: if self.is_interacting is False: self.logger.info("Not in interaction mode!") return False self.logger.info("isExecuted data received: {}".format(data_dict)) self.execution_result = data_dict["isExecuted"]["executionResult"] self.execute_next_block() except Exception as e: self.logger.error("Error while extracting isExecuted block: {} | {}".format(data_dict, e)) def on_tablet_input(self, data_dict=None): self.tablet_input = "" self.execution_result = "" try: if self.is_interacting is False: self.logger.info("Not in interaction mode!") return False self.logger.info("Tablet data received: {}".format(data_dict)) self.tablet_input = data_dict["tabletInput"] self.execution_result = data_dict["tabletInput"] self.execute_next_block() except Exception as e: self.logger.error("Error while extracting tablet data: {} | {}".format(data_dict, e)) def on_face_detected(self, data_dict=None): try: self.logger.info("Received a detected face: {}".format(data_dict)) self.face_detected_signal.emit(data_dict["faceDetected"]) if self.is_interacting: self.face_size_dict["{}".format(time.time()).replace(".", "_")] = data_dict["faceDetected"] except Exception as e: self.logger.error("Error while extracting face size: {}".format(e)) def on_disengaged(self, data_dict=None): try: self.logger.info("Disengaged data received: {}".format(data_dict)) self.execution_result = "" self.stop_playing() except Exception as e: self.logger.error("Error while extracting disengaged data: {} | {}".format(data_dict, e)) def update_speech_certainty(self, speech_certainty=40.0): self.db_stream_controller.update_one(self.db_stream_controller.interaction_collection, data_key="speechCertainty", data_dict={"speechCertainty": speech_certainty, "timestamp": time.time()}) def update_db_data(self, data_key, data_value): self.db_stream_controller.update_one(self.db_stream_controller.interaction_collection, data_key=data_key, data_dict={data_key: data_value, "timestamp": time.time()}) def wakeup_robot(self): success = False try: self.db_stream_controller.update_one(self.db_stream_controller.interaction_collection, data_key="wakeUpRobot", data_dict={"wakeUpRobot": True, "timestamp": time.time()}) success = True except Exception as e: self.logger.error("Error while waking up the robot! | {}".format(e)) finally: return success def rest_robot(self): try: self.db_stream_controller.update_one(self.db_stream_controller.interaction_collection, data_key="restRobot", data_dict={"restRobot": True, "timestamp": time.time()}) except Exception as e: self.logger.error("Error while setting the robot posture to rest: {}".format(e)) # TOUCH # ------ def enable_touch(self): self.db_stream_controller.update_one(self.db_stream_controller.interaction_collection, data_key="enableTouch", data_dict={"enableTouch": True, "timestamp": time.time()}) # BEHAVIORS # --------- def animate(self, animation_name=None): self.db_stream_controller.update_one(self.db_stream_controller.interaction_collection, data_key="animateRobot", data_dict={"animateRobot": {"animation": animation_name, "message": ""}, "timestamp": time.time()}) def animated_say(self, message=None, animation_name=None): self.db_stream_controller.update_one(self.db_stream_controller.interaction_collection, data_key="animateRobot", data_dict={ "animateRobot": {"animation": animation_name, "message": message}, "timestamp": time.time()}) def customized_say(self, interaction_block): if interaction_block is None: return interaction_block.interaction_start_time = time.time() block_to_execute = interaction_block.clone() if "{answer}" in block_to_execute.message and self.execution_result: block_to_execute.message = block_to_execute.message.format(answer=self.execution_result.lower()) self.db_stream_controller.update_one(self.db_stream_controller.interaction_collection, data_key="interactionBlock", data_dict={"interactionBlock": block_to_execute.to_dict, "timestamp": time.time()}) # SPEECH # ------ def say(self, message=None): to_say = "Hello!" if message is None else message if message is None: self.logger.info(to_say) self.animated_say(message=to_say) def on_start_interaction(self, data_dict=None): if self.is_interacting: self.logger.info("Interaction is in progress...") return False self.logger.info("Starting the interaction!") self.start_playing(int_block=self.get_starting_block()) def start_playing(self, int_block): if int_block is None: self.is_interacting = False self.logger.warning("Couldn't start the interaction!") return False self.is_interacting = True self.previous_interaction_block = None self.current_interaction_block = int_block self.current_interaction_block.execution_mode = ExecutionMode.NEW self.logger.debug("Started playing the blocks") self.execute_next_block() # start interacting def stop_playing(self): # self.tablet_image(hide=True) self.is_interacting = False self.db_stream_controller.update_one(self.db_stream_controller.interaction_collection, data_key="resumeEngagement", data_dict={"resumeEngagement": True, "timestamp": time.time()}) self.has_finished_playing_observers.notify_all(True) self.logger.info("Stopped interacting.") def get_starting_block(self): try: # check if the scene contains a valid start block block = self.block_controller.get_block(pattern="start") if block is None: self.logger.warning("The scene doesn't contain a starting block! " "Please add a 'START' block to play the interaction!") return None self.logger.info("Found a starting block.") return block.parent except Exception as e: self.logger.error("Error while getting the start block: {}".format(e)) return None def execute_next_block(self): try: self.logger.info("Executing next block.\n") # self.block_controller.clear_selection() connecting_edge = None # check for remaining actions; otherwise, continue if self.current_interaction_block.action_command and \ self.current_interaction_block.execution_mode is ExecutionMode.EXECUTING: self.execute_action_command() else: # check if the interaction has just started if self.previous_interaction_block is None: self.previous_interaction_block = self.current_interaction_block else: # get the next block to say self.current_interaction_block, connecting_edge = self.get_next_interaction_block() if self.verify_current_interaction_block() is False: return False # change the block status self.current_interaction_block.execution_mode = ExecutionMode.EXECUTING # update selection # self.current_interaction_block.set_selected(True) # if connecting_edge is not None: # connecting_edge.set_selected(True) # send a request to say the robot message self.customized_say(interaction_block=self.current_interaction_block) except Exception as e: self.logger.error("Error while executing next block: {}".format(e)) def get_next_interaction_block(self): if self.current_interaction_block is None: return None next_block = None connecting_edge = None self.logger.info("Getting the next interaction block...") try: # 1- Check for the design module and execute it if self.current_interaction_block.design_module and \ self.current_interaction_block.execution_mode is ExecutionMode.EXECUTING: self.execute_interaction_module() # 2- When the interaction module exists, get the next block from it if self.current_interaction_block.is_hidden and self.interaction_module: connecting_edge = None next_block = self.interaction_module.get_next_interaction_block(self.current_interaction_block, self.execution_result) # check if reached the end of the interaction_module if next_block is None and self.previous_interaction_block: self.current_interaction_block = self.interaction_module.origin_block # 3- Otherwise, Get the next block from the current design if next_block is None: next_block, connecting_edge = self.current_interaction_block.get_next_interaction_block( execution_result=self.execution_result) # Change current block status self.current_interaction_block.execution_mode = ExecutionMode.COMPLETED self.current_interaction_block.execution_result = self.execution_result self.current_interaction_block.interaction_end_time = time.time() # update previous block self.previous_interaction_block = self.current_interaction_block self.executed_blocks.append(self.current_interaction_block) return next_block, connecting_edge except Exception as e: self.logger.error("Error while getting the next block! {}".format(e)) return next_block, connecting_edge def engagement(self, start=False): """ @param start = bool """ self.logger.info("Engagement called with start = {}".format(start)) self.db_stream_controller.update_one(self.db_stream_controller.interaction_collection, data_key="startEngagement", data_dict={"startEngagement": start, "timestamp": time.time()}) def verify_current_interaction_block(self): # if there are no more blocks, stop interacting if self.current_interaction_block is None: # stop interacting self.stop_playing() return False return True def execute_interaction_module(self): self.interaction_module = ModuleFactory.create_module(self.current_interaction_block.design_module, self.current_interaction_block, self.block_controller) self.logger.info("Interaction module: {}".format(self.interaction_module)) if self.interaction_module is None: return next_b = self.interaction_module.execute_module() self.update_communication_style() if next_b is not None: next_b.is_hidden = True # just in case! self.current_interaction_block.execution_mode = ExecutionMode.COMPLETED self.previous_interaction_block = self.current_interaction_block self.current_interaction_block = next_b def update_communication_style(self): try: if "person" in self.interaction_module.filename.lower(): self.comm_style = CommunicationStyle.PERSON_ORIENTED elif "task" in self.interaction_module.filename.lower(): self.comm_style = CommunicationStyle.TASK_ORIENTED except Exception as e: self.logger.error("Error while setting the communication style: {}".format(e)) def execute_action_command(self): # check for remaining actions if self.current_interaction_block.has_action(action_type=ActionCommand.PLAY_MUSIC): self.on_music_mode() return True elif self.current_interaction_block.has_action(action_type=ActionCommand.WAIT): self.on_wait_mode() return True elif self.current_interaction_block.has_action(action_type=ActionCommand.GET_RESERVATIONS): self.on_get_reservations() return True return False def on_get_reservations(self): get_reservations_command = self.current_interaction_block.action_command if get_reservations_command is not None: reservations = get_reservations_command.execute() def on_wait_mode(self): wait_time = 1 # 1s try: if self.current_interaction_block is not None: self.current_interaction_block.execution_mode = ExecutionMode.COMPLETED wait_command = self.current_interaction_block.action_command if wait_command is not None: wait_time = wait_command.wait_time except Exception as e: self.logger.error("Error while setting wait time! {}".format(e)) finally: self.logger.debug("Waiting for {} s".format(wait_time)) QTimer.singleShot(wait_time * 1000, self.execute_next_block) def on_music_mode(self): if self.music_controller is None: self.logger.debug("Music player is not connected! Will skip playing music.") self.on_music_stop() else: self.current_interaction_block.action_command.music_controller = self.music_controller success = self.current_interaction_block.action_command.execute() if success is True: self.logger.debug("Playing now: {}".format(self.current_interaction_block.action_command.track)) # TODO: specify wait time as track time when play_time is < 0 # use action play time wait_time = self.current_interaction_block.action_command.play_time if wait_time <= 0: wait_time = 30 # wait for 30s then continue anim_key = self.current_interaction_block.action_command.animations_key if anim_key is None or anim_key == "": QTimer.singleShot(int(wait_time) * 1000, self.on_music_stop) else: self.on_animation_mode(music_command=self.current_interaction_block.action_command, animation_time=int(wait_time)) # QTimer.singleShot(wait_time * 1000, self.on_music_stop) else: self.logger.warning("Unable to play music! {}".format(self.music_controller.warning_message)) self.on_music_stop() def on_animation_mode(self, music_command, animation_time=0): self.music_command = music_command self.animations_lst = config_helper.get_animations()[music_command.animations_key] self.animation_time = animation_time self.animation_counter = -1 self.timer_helper.start() self.execute_next_animation() def on_animation_completed(self, val=None): QTimer.singleShot(3000, self.execute_next_animation) def execute_next_animation(self): if self.music_command is None or len(self.animations_lst) == 0: QTimer.singleShot(1000, self.on_music_stop) elif self.timer_helper.elapsed_time() <= self.animation_time - 4: # use 4s threshold # repeat the animations if the counter reached the end of the lst self.animation_counter += 1 if self.animation_counter >= len(self.animations_lst): self.animation_counter = 0 animation, message = self.get_next_animation(self.animation_counter) if message is None or message == "": self.animate(animation_name=animation) else: self.animated_say(message=message, animation_name=animation) else: remaining_time = self.animation_time - self.timer_helper.elapsed_time() QTimer.singleShot(1000 if remaining_time < 0 else remaining_time * 1000, self.on_music_stop) def get_next_animation(self, anim_index): anim, msg = ("", "") try: animation_dict = self.animations_lst[anim_index] if len(animation_dict) > 0: anim = animation_dict.keys()[0] msg = animation_dict[anim] except Exception as e: self.logger.error("Error while getting next animation! {}".format(e)) finally: return anim, msg def on_music_stop(self): self.logger.debug("Finished playing music.") try: if self.current_interaction_block is not None: self.current_interaction_block.execution_mode = ExecutionMode.COMPLETED if self.music_controller is not None: self.music_controller.pause() except Exception as e: self.logger.error("Error while stopping the music! {}".format(e)) finally: self.execute_next_block() # TABLET # ------ def tablet_image(self, hide=False): self.db_stream_controller.update_one(self.db_stream_controller.interaction_collection, data_key="hideTabletImage", data_dict={"hideTabletImage": True, "timestamp": time.time()}) # PROPERTIES # ========== @property def is_interacting(self): return self._is_interacting @is_interacting.setter def is_interacting(self, val=False): self._is_interacting = val self.db_stream_controller.update_one(self.db_stream_controller.interaction_collection, data_key="isInteracting", data_dict={"isInteracting": val, "timestamp": time.time()})
class ESGraphicsViewController(QGraphicsView): def __init__(self, graphics_scene, parent=None): super(ESGraphicsViewController, self).__init__(parent) self.logger = logging.getLogger("GraphicsView") self.graphics_scene = graphics_scene self._init_ui() self.setScene(self.graphics_scene) self.mode = Mode.NO_OP # no operation self.zoom_in_factor = 1.1 self.zoom_clamp = True self.zoom = 0 self.zoom_step = 1 self.zoom_range = [-20, 1] self.drag_edge = None self.drag_start_socket = None # to deal with event observers self.drag_enter_observers = Observable() self.drop_observers = Observable() self.block_selected_observers = Observable() self.no_block_selected_observers = Observable() self.invalid_edge_observers = Observable() self.update() self.show() def _init_ui(self): self.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform) self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) # self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setDragMode(QGraphicsView.RubberBandDrag) # enable drop self.setAcceptDrops(True) def keyPressEvent(self, event): if event.key() in (Qt.Key_Delete, Qt.Key_Backspace): self.delete_selected() # elif event.key() == Qt.Key_S and event.modifiers() & Qt.ControlModifier: # self.graphics_scene.scene.save_scene("graph.json") # elif event.key() == Qt.Key_L and event.modifiers() & Qt.ControlModifier: # self.graphics_scene.scene.load_scene("graph.json") # elif event.key() == Qt.Key_Z \ # and event.modifiers() & Qt.ControlModifier \ # and not event.modifiers() & Qt.ShiftModifier: # self.graphics_scene.scene.history.undo() # elif event.key() == Qt.Key_Z \ # and event.modifiers() & Qt.ControlModifier \ # and event.modifiers() & Qt.ShiftModifier: # self.graphics_scene.scene.history.redo() elif event.key() == Qt.Key_H: to_log = "\nHISTORY:\ttotal = {} -- step = {}\n".format( len(self.graphics_scene.scene.history.history_stack), self.graphics_scene.scene.history.current_step) index = 0 for item in self.graphics_scene.scene.history.history_stack: to_log = "{}\t#{} -- {}\n".format(to_log, index, item['description']) index += 1 self.logger.debug(to_log) super(ESGraphicsViewController, self).keyPressEvent(event) def dragEnterEvent(self, event): self.drag_enter_observers.notify_all(event) def dropEvent(self, event): self.drop_observers.notify_all(event) def mouseMoveEvent(self, event): if self.mode == Mode.DRAG_EDGE: pos = self.mapToScene(event.pos()) self.drag_edge.update_destination(pos.x(), pos.y()) super(ESGraphicsViewController, self).mouseMoveEvent(event) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.left_mouse_button_press(event) elif event.button() == Qt.RightButton: self.right_mouse_button_press(event) elif event.button() == Qt.MiddleButton: self.middle_mouse_button_press(event) else: super(ESGraphicsViewController, self).mousePressEvent(event) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: self.left_mouse_button_release(event) elif event.button() == Qt.RightButton: self.right_mouse_button_release(event) elif event.button() == Qt.MiddleButton: self.middle_mouse_button_release(event) else: super(ESGraphicsViewController, self).mouseReleaseEvent(event) def left_mouse_button_press(self, event): item = self.get_clicked_item(event) # self.logger.debug("Item {} is clicked.".format(item)) if hasattr(item, "block"): self.logger.debug("Selected item block: {}".format(item.block)) # send the item not the event self.block_selected_observers.notify_all(item.block) else: self.no_block_selected_observers.notify_all(event) # if item is None: # drag the scene # pass # self.setDragMode(QGraphicsView.ScrollHandDrag) if type(item) is ESGraphicsSocket: if self.mode == Mode.NO_OP: self.edge_drag_start(item) return if self.mode == Mode.DRAG_EDGE: success = self.edge_drag_end(item) if success: return super(ESGraphicsViewController, self).mousePressEvent(event) def left_mouse_button_release(self, event): item = self.get_clicked_item(event) # if item is None: # pass # self.setDragMode(QGraphicsView.NoDrag) if self.mode == Mode.DRAG_EDGE: # bypass the first click-release on the same socket if type(item) is ESGraphicsSocket: if item.socket != self.drag_start_socket: success = self.edge_drag_end(item) if success: return # if item is not None and self.dragMode() == QGraphicsView.RubberBandDrag and self.mode is Mode.NO_OP: # print("selection changed") # self.graphics_scene.scene.history.store("Selection changed") super(ESGraphicsViewController, self).mouseReleaseEvent(event) def right_mouse_button_press(self, event): super(ESGraphicsViewController, self).mousePressEvent(event) item = self.get_clicked_item(event) if item is None: # displays the number of blocks and edges in the scene to_log = "\nSCENE:\n\tBlocks:" for block in self.graphics_scene.scene.blocks: to_log = "{}\n\t\t{}".format(to_log, block) to_log = "{}\n\tEdges:".format(to_log) for edge in self.graphics_scene.scene.edges: to_log = "{}\n\t\t{}".format(to_log, edge) self.logger.info(to_log) elif isinstance(item, ESGraphicsSocket): self.logger.info("{} has edges: {}".format(item.socket, item.socket.edges)) elif isinstance(item, ESGraphicsEdge): self.logger.debug("{} connecting {} & {}".format( item.edge, item.edge.start_socket.block.title, item.edge.end_socket.block.title)) self.logger.debug("Edge is in {}: {} | in {}: {}".format( item.edge.start_socket, item.edge in item.edge.start_socket.edges, item.edge.end_socket, item.edge in item.edge.end_socket.edges)) def right_mouse_button_release(self, event): super(ESGraphicsViewController, self).mouseReleaseEvent(event) def middle_mouse_button_press(self, event): super(ESGraphicsViewController, self).mousePressEvent(event) def middle_mouse_button_release(self, event): super(ESGraphicsViewController, self).mouseReleaseEvent(event) def wheelEvent(self, event): modifiers = QApplication.keyboardModifiers() if modifiers == Qt.ControlModifier: self.zoom_scene(delta_y=event.angleDelta().y()) else: super(ESGraphicsViewController, self).wheelEvent(event) def zoom_scene(self, delta_y): # zoom factor zoom_out_factor = 1 / self.zoom_in_factor # compute zoom if delta_y > 0: zoom_factor = self.zoom_in_factor self.zoom += self.zoom_step else: zoom_factor = zoom_out_factor self.zoom -= self.zoom_step clamped = False if self.zoom < self.zoom_range[0]: self.zoom, clamped = self.zoom_range[0], True if self.zoom > self.zoom_range[1]: self.zoom, clamped = self.zoom_range[1], True # scene scale if not clamped or self.zoom_clamp is False: self.scale(zoom_factor, zoom_factor) ### # Helper Methods ### def delete_selected(self): self.delete_selected_edges() self.delete_selected_blocks() self.graphics_scene.scene.store("Deleted selected items") def delete_selected_blocks(self): for item in self.graphics_scene.selectedItems(): if hasattr(item, "block"): item.block.remove() def delete_selected_edges(self): for item in self.graphics_scene.selectedItems(): if isinstance(item, ESGraphicsEdge): item.edge.remove() def get_clicked_item(self, event): pos = event.pos() return self.itemAt(pos) def edge_drag_start(self, item): self.mode = Mode.DRAG_EDGE self.drag_start_socket = item.socket # create a new edge with dotted line self.drag_edge = Edge(scene=self.graphics_scene.scene, start_socket=item.socket, end_socket=None, edge_type=EdgeType.BEZIER) def edge_drag_end(self, item): # update mode self.mode = Mode.NO_OP success = False try: # remove dragged edge self.drag_edge.remove() self.drag_edge = None if type(item) is ESGraphicsSocket: # check if the connection is valid if self.is_valid_connection(item.socket): # create edge Edge(scene=self.graphics_scene.scene, start_socket=self.drag_start_socket, end_socket=item.socket, edge_type=EdgeType.BEZIER) # store self.graphics_scene.scene.store("New edge created") success = True except Exception as e: self.logger.error("Error while ending drag! {}".format(e)) self.invalid_edge_observers.notify_all( "Error while creating the edge!") finally: return success def is_valid_connection(self, other_socket): # check if it's the same socket if other_socket == self.drag_start_socket: return False # connected sockets should have opposite types (input vs output) if other_socket.socket_type == self.drag_start_socket.socket_type: self.invalid_edge_observers.notify_all( "* Cannot connect two sockets of the same type ({})".format( self.drag_start_socket.socket_type.name)) return False # check if one of the sockets is connected to the other's block if self.drag_start_socket.is_connected_to_block(other_socket.block) \ or other_socket.is_connected_to_block(self.drag_start_socket.block): return False # check the number of allowed edges for the each block if not (self.drag_start_socket.can_have_more_edges() and other_socket.can_have_more_edges()): self.invalid_edge_observers.notify_all( "* The Edge cannot be created: " "The output has reached the max number of allowed edges!") return False # connect to a block once # if other_socket.block.is_connected_to(self.drag_start_socket.block): # return False # the connection is valid return True
class BlockManagerWidget(QWidget): def __init__(self, scene, parent=None): super(BlockManagerWidget, self).__init__(parent) self.logger = logging.getLogger("BlockManagerWidget") self._init_ui(scene) # Observables self.drag_enter_observers = Observable() self.drop_observers = Observable() self.block_selected_observers = Observable() self.no_block_selected_observers = Observable() self.invalid_edge_observers = Observable() self.right_click_block_observers = Observable() def _init_ui(self, scene): self.setGeometry(200, 200, 800, 600) # layout self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) # Graphics scene self.scene = scene # Graphics View self.blocks_view = ESGraphicsViewController(self.scene.graphics_scene, self) # add observers self.blocks_view.drag_enter_observers.add_observer(self.on_drag_enter) self.blocks_view.drop_observers.add_observer(self.on_drop) self.blocks_view.block_selected_observers.add_observer(self.on_block_selected) self.blocks_view.no_block_selected_observers.add_observer(self.on_no_block_selected) self.blocks_view.invalid_edge_observers.add_observer(self.on_invalid_edge) # set layout self.layout.addWidget(self.blocks_view) self.setLayout(self.layout) self.setWindowTitle("Block Manager") self.update_widget() self.show() def update_widget(self): self.update() # self.blocks_view.update() def contextMenuEvent(self, event): item = self.get_item_at(event.pos()) if hasattr(item, "block"): self.logger.debug("item has block attribute: {}".format(item)) # notify observers self.right_click_block_observers.notify_all(event) super(BlockManagerWidget, self).contextMenuEvent(event) def get_scene_position(self, mouse_position): try: return self.blocks_view.mapToScene(mouse_position) except Exception as e: self.logger.error("Error while mapping mouse position to scene! {}".format(e)) def zoom_scene(self, val): self.blocks_view.zoom_scene(delta_y=val) def get_item_at(self, pos): return self.blocks_view.itemAt(pos) def delete_selected(self): self.blocks_view.delete_selected() def clean_up(self): # called on exit del self.scene del self.blocks_view ### # Event Listeners ### def on_drag_enter(self, event=None): self.drag_enter_observers.notify_all(event) def on_drop(self, event=None): self.drop_observers.notify_all(event) def on_block_selected(self, event=None): self.block_selected_observers.notify_all(event) def on_no_block_selected(self, event=None): self.no_block_selected_observers.notify_all(event) def on_invalid_edge(self, event=None): self.invalid_edge_observers.notify_all(event)
class SimulationController(object): def __init__(self, block_controller, music_controller=None, parent=None): self.logger = logging.getLogger("SimulationController") self.block_controller = block_controller self.music_controller = music_controller self.user_input = None self.interaction_log = None self.simulation_dock_widget = None self._init_dock_widget(parent) self.user_turn = False self.timer_helper = TimerHelper() self.animations_lst = [] self.current_interaction_block = None self.previous_interaction_block = None self.connecting_edge = None self.execution_result = None self.stop_playing = False self.finished_simulation_observable = Observable() def _init_dock_widget(self, parent): self.simulation_dock_widget = QtWidgets.QDockWidget( "Simulation", parent) size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(0) size_policy.setHeightForWidth( self.simulation_dock_widget.sizePolicy().hasHeightForWidth()) self.simulation_dock_widget.setSizePolicy(size_policy) self.simulation_dock_widget.setMinimumSize(QtCore.QSize(98, 150)) self.simulation_dock_widget.setFloating(False) self.simulation_dock_widget.setFeatures( QtWidgets.QDockWidget.AllDockWidgetFeatures) self.simulation_dock_widget.setAllowedAreas( QtCore.Qt.AllDockWidgetAreas) self.simulation_dock_widget.setObjectName("simulationDockWidget") # layout widget_content = QtWidgets.QWidget() widget_content.setObjectName("simulationDockWidgetContents") grid_layout = QtWidgets.QGridLayout(widget_content) grid_layout.setContentsMargins(11, 11, 11, 11) grid_layout.setSpacing(6) grid_layout.setObjectName("simulationGridLayout") # display text edit self.interaction_log = QtWidgets.QTextEdit(widget_content) self.interaction_log.setAcceptDrops(False) self.interaction_log.setAutoFillBackground(True) self.interaction_log.setStyleSheet("background: white") self.interaction_log.setUndoRedoEnabled(False) self.interaction_log.setReadOnly(True) self.interaction_log.setAcceptRichText(True) self.interaction_log.setObjectName("simulationTextEdit") grid_layout.addWidget(self.interaction_log, 0, 0, 1, 1) # user input self.user_input = QtWidgets.QLineEdit(widget_content) self.user_input.setPlaceholderText("User Input") self.user_input.returnPressed.connect(self.check_user_input) self.user_input.setObjectName("simulationLineEdit") grid_layout.addWidget(self.user_input, 1, 0, 1, 1) widget_content.setLayout(grid_layout) self.simulation_dock_widget.setWidget(widget_content) def reset(self): self.block_controller.clear_selection() self.interaction_log.clear() self.user_turn = False self.current_interaction_block = None self.previous_interaction_block = None self.connecting_edge = None self.execution_result = None self.stop_playing = False def start_simulation(self, int_block): if int_block is None: return self.reset() self.simulation_dock_widget.setFocus() self.simulation_dock_widget.raise_() self.current_interaction_block = int_block self.execute_next_interaction_block() def execute_next_interaction_block(self): self.logger.debug("Getting the next interaction block...") if self.current_interaction_block is None \ or self.user_turn is True: return self.logger.debug("Execution Result: {}".format(self.execution_result)) if self.previous_interaction_block is None: # simulation just started # update previous block only self.previous_interaction_block = self.current_interaction_block else: # update previous and next blocks self.previous_interaction_block = self.current_interaction_block self.current_interaction_block, self.connecting_edge = \ self.current_interaction_block.get_next_interaction_block(execution_result=self.execution_result) # reset execution_result self.execution_result = None self.simulate_interaction() def simulate_interaction(self): self.block_controller.clear_selection() # if there are no more blocks, stop interacting if self.current_interaction_block is None or self.stop_playing is True: self.update_interaction_log( robot_message="Finished the interaction!") self.finished_simulation_observable.notify_all(True) return True else: # execute the block self.logger.debug("Executing: {}".format( self.current_interaction_block.name)) self.current_interaction_block.set_selected(True) if self.connecting_edge is not None: self.connecting_edge.set_selected(True) self.update_interaction_log( robot_message=self.current_interaction_block.message) if "question" in self.current_interaction_block.pattern.lower(): self.user_turn = True return True # check actions if self.current_interaction_block.has_action( action_type=ActionCommand.PLAY_MUSIC): self.on_music_mode() elif self.current_interaction_block.has_action( action_type=ActionCommand.WAIT): self.on_wait_mode() else: QTimer.singleShot(1500, self.execute_next_interaction_block) def check_user_input(self): user_input = self.user_input.text() self.update_interaction_log(user_message=user_input) # clear input self.user_input.clear() try: # validate user input if user_input.lower() == "exit": # exit the interaction self.user_turn = False self.execution_result = user_input self.update_interaction_log(robot_message="Ok, i'm exiting!") QTimer.singleShot(1000, self.execute_next_interaction_block) elif self.current_interaction_block.is_valid_user_input( user_input=user_input) is True: self.user_turn = False self.execution_result = user_input self.execute_next_interaction_block() else: self.update_interaction_log( robot_message= "Sorry, I didn't get your answer. Please try again!") except Exception as e: self.logger.error("Error while verifying user input! {}".format(e)) def on_wait_mode(self): wait_command = self.current_interaction_block.action_command if wait_command is None: QTimer.singleShot(1000, self.execute_next_interaction_block) else: QTimer.singleShot(wait_command.wait_time * 1000, self.execute_next_interaction_block) def on_music_mode(self): music_command = self.current_interaction_block.action_command if self.music_controller is None or music_command is None: QTimer.singleShot(1000, self.execute_next_interaction_block) else: music_command.music_controller = self.music_controller success = music_command.execute() if success is True: message = "Playing now: {}".format(music_command.track) self.update_interaction_log(robot_message=message) # TODO: specify wait time as track time when play_time is < 0 # use action play time wait_time = music_command.play_time if wait_time <= 0: wait_time = 30 # wait for 30s then continue anim_key = music_command.animations_key if anim_key is None or anim_key == "": QTimer.singleShot(wait_time * 1000, self.on_music_stop) else: self.timer_helper.start() self.animations_lst = config_helper.get_animations()[ music_command.animations_key] self.on_animation_mode(music_command=music_command, animation_time=wait_time, counter=0) else: warning = "Unable to play music! {}".format( self.music_controller.warning_message) self.update_interaction_log(robot_message=warning) QTimer.singleShot(2000, self.execute_next_interaction_block) def on_animation_mode(self, music_command, animation_time=0, counter=0): if music_command is None: QTimer.singleShot(1000, self.on_music_stop) if self.timer_helper.elapsed_time( ) <= animation_time - 4: # 4s threshold anim_index = 0 if counter >= len(self.animations_lst) else counter anim, msg = self.get_next_animation(anim_index) robot_message = "{} - Executing {}".format( "Animation time" if msg is None else msg, anim) self.update_interaction_log(robot_message=robot_message) QTimer.singleShot( 4000, lambda: self.on_animation_mode( music_command, animation_time, anim_index + 1)) else: remaining_time = animation_time - self.timer_helper.elapsed_time() QTimer.singleShot( 1000 if remaining_time < 0 else remaining_time * 1000, self.on_music_stop) def get_next_animation(self, anim_index): anim, msg = ("", "") try: animation_dict = self.animations_lst[anim_index] if len(animation_dict) > 0: anim = animation_dict.keys()[0] msg = animation_dict[anim] except Exception as e: self.logger.error( "Error while getting next animation! {}".format(e)) finally: return anim, msg def on_music_stop(self): self.update_interaction_log(robot_message="Let' continue!") self.music_controller.pause() self.execute_next_interaction_block() def update_interaction_log(self, robot_message=None, user_message=None): if robot_message is not None and robot_message != "": self._append_interaction_text(name="Robot", color_name="green", message=robot_message) elif self.user_turn is True: # user_input is not None # to prevent logging users' entered input when it's not their turn self._append_interaction_text(name="User", color_name="maroon", message=user_message) def _append_interaction_text(self, name, color_name, message): self.interaction_log.setTextColor(QtGui.QColor(color_name)) self.interaction_log.append("{}:".format(name)) self.interaction_log.setTextColor(QtGui.QColor("black")) self.interaction_log.append("{}".format(message)) self.logger.debug("{}: {}".format(name, message))
class SpeechHandler(object): def __init__(self, session): self.logger = logging.getLogger("Speech Handler") self.session = session self.tts = self.session.service("ALTextToSpeech") self.animated_speech = self.session.service("ALAnimatedSpeech") self.memory = self.session.service("ALMemory") self.speech_recognition = self.session.service("ALSpeechRecognition") self.audio = session.service("ALAudioDevice") self.keyword_observers = Observable() self.speech_certainty = 0.4 self.voice_pitch = 100 self.voice_speed = 85 self.current_keywords = [] self.is_listening = False self.start_time = time.time() # add a listener to the keyword stream self.keyword_listener = None self.keyword_events(subscribe=True) # SUBSCRIBE # ========= def keyword_events(self, subscribe=True): try: if subscribe: self.keyword_listener = self.memory.subscriber( "WordRecognized") self.keyword_listener.signal.connect(self.on_keyword) else: self.keyword_listener = None except Exception as e: self.logger.error( "Error while subscribing to recognized word event: {}".format( e)) # KEYWORDS # ======== def on_keyword(self, value=None): self.logger.info("OnKeyword: value = {}".format(value)) if (not self.is_listening) and (value is None or len(value) == 0): self.logger.info("No keywords!") else: try: certainty = value[1] if certainty >= self.speech_certainty: keyword = value[0] if keyword in self.current_keywords: self.logger.info( "Detected keyword is in the list: {}".format( value)) self.is_listening = False self.clear_keywords() self.keyword_stream(start=False) self.keyword_observers.notify_all(keyword) else: self.logger.info( "Keyword received '{}' is not in the list {}". format(keyword, self.current_keywords)) else: self.logger.info( "OnKeyword received with low certainty: {}".format( value)) except Exception as e: self.logger.error( "Error while getting the received answer! | {}".format(e)) def add_keywords(self, keywords=None): # add keywords = [array of strings] to listen to if keywords: try: self.logger.info("Adding keywords: {}".format(keywords)) self.speech_recognition.pause(True) self.speech_recognition.setVocabulary(keywords, False) self.speech_recognition.pause(False) except Exception as e: self.logger.error( "Error while adding keywords: {} | {}".format(keywords, e)) def remove_keywords(self, keywords=None): if keywords is None: keywords = [] try: self.speech_recognition.pause(True) # self.speech_recognition.setVocabulary([k for k in self.current_keywords if k not in keywords], False) except Exception as e: self.logger.error("Error while removing keywords: {} | {}".format( keywords, e)) def clear_keywords(self): self.current_keywords = [] # self.remove_keywords() def keyword_stream(self, start=False): # start/close the speech recognition engine self.logger.info("Speech recognition engine {}.".format( "is starting" if start else "is stopped")) if start: self.speech_recognition.subscribe("SpeechHandler") else: self.speech_recognition.unsubscribe("SpeechHandler") # Listener # ========= def on_speech_event(self, event_name=None, task_id=None, subscriber_identifier=None): self.logger.info("Speech Event: {} | {} | {}".format( event_name, task_id, subscriber_identifier)) self.on_start_listening() def on_start_listening(self, results=None): self.is_listening = True self.add_keywords(keywords=self.current_keywords) self.keyword_stream(start=True) # ====== # SPEECH # ====== def say(self, message="Hi"): text = "\\vct={}\\\\rspd={}\\{}".format(int(self.voice_pitch), int(self.voice_speed), message) self.tts.say(text) def animated_say(self, message="", animation_name=None): text = "\\vct={}\\\\rspd={}\\{}".format(int(self.voice_pitch), int(self.voice_speed), message) if animation_name is None: self.animated_speech.say( text, {"bodyLanguageMode": "contextual"}) # vs random else: self.animated_speech.say( '^start({}) {} ^wait({})'.format(animation_name, text, animation_name), {"bodyLanguageMode": "contextual"}) def set_volume(self, level=50.0): vol = int(level) if level > 1 else int(level * 100) self.logger.info("Setting volume to: {}".format(vol)) self.audio.setOutputVolume(vol) def set_language(self, name="English"): self.tts.setLanguage(name) self.speech_recognition.setLanguage(name) # MEMORY # ====== def insert(self, data_dict=None): try: for key in data_dict.keys(): self.memory.insertData(key, data_dict[key]) except Exception as e: print("Error while inserting '{}' into memory: {}".format( data_dict, e)) def raise_event(self, event_name, event_value): self.logger.info("Raised event '{}' to load '{}'".format( event_name, event_value)) self.memory.raiseEvent(event_name, event_value) def get_speech_event(self): return self.memory.subscriber("ALAnimatedSpeech/EndOfAnimatedSpeech") # PROPERTIES # ========== @property def speech_certainty(self): return self.__speech_certainty @speech_certainty.setter def speech_certainty(self, val): val = abs(float(val)) self.__speech_certainty = val if (0 <= val <= 1) else (val / 100.0) # HELPER def print_data(self, result): self.logger.info('Result received: {}'.format(result))
class SpeechHandler(object): def __init__(self, session): self.logger = logging.getLogger("Speech Handler") self.session = session self.keyword_observers = Observable() self.speech_certainty = 0.4 self.voice_pitch = 100 self.voice_speed = 85 self.current_keywords = [] self.is_listening = False self.start_time = time.time() # add a listener to the keyword stream self.session.subscribe(self.on_keyword, "rom.optional.keyword.stream") # KEYWORDS # ======== @inlineCallbacks def on_keyword(self, frame): if (not self.is_listening) and (frame is None or len(frame) == 0): self.logger.info("No frames!") else: try: certainty = frame["data"]["body"]["certainty"] if certainty >= self.speech_certainty: keyword = frame["data"]["body"]["text"] if keyword in self.current_keywords: self.logger.info( "Detected keyword is in the list: {}".format( frame["data"])) self.is_listening = False yield self.clear_keywords() yield self.keyword_stream(start=False) self.keyword_observers.notify_all(keyword) else: self.logger.info( "Keyword received '{}' is not in the list {}". format(keyword, self.current_keywords)) else: self.logger.info( "OnKeyword received with low certainty: {}".format( frame)) except Exception as e: self.logger.error( "Error while getting the received answer! | {}".format(e)) def add_keywords(self, keywords=None): # add keywords = [array of strings] to listen to self.session.call("rom.optional.keyword.add", keywords=[] if keywords is None else keywords) def remove_keywords(self, keywords=None): self.session.call("rom.optional.keyword.remove", keywords=[] if keywords is None else keywords) def clear_keywords(self): self.current_keywords = [] self.session.call("rom.optional.keyword.clear") def keyword_stream(self, start=False): # start/close the keyword stream self.session.call("rom.optional.keyword.stream" if start else "rom.optional.keyword.close") # Listener # ========= def on_start_listening(self, results=None): self.is_listening = True self.add_keywords(keywords=self.current_keywords) self.keyword_stream(start=True) # ====== # SPEECH # ====== def say(self, message="Hi"): text = "\\vct={}\\\\rspd={}\\{}".format(int(self.voice_pitch), int(self.voice_speed), message) return self.session.call("rom.optional.tts.say", text=text) def animated_say(self, message="", animation_name=None): text = "\\vct={}\\\\rspd={}\\{}".format(int(self.voice_pitch), int(self.voice_speed), message) return self.session.call("rom.optional.tts.animate", text=text) def set_volume(self, level=50.0): vol = int(level) if level > 1 else int(level * 100) self.logger.info("Setting volume to: {}".format(vol)) self.session.call("rom.actuator.audio.volume", volume=vol) def set_language(self, name="en"): self.session.call("rom.optional.tts.language", lang=u"{}".format(name)) self.session.call("rie.dialogue.config.language", lang=u"{}".format(name)) # MEMORY # ======= def insert(self, data_dict=None): try: self.session.call("rom.optional.tts.insert_data", data_dict={} if data_dict is None else data_dict) except Exception as e: self.logger.error( "Error while inserting '{}' into memory: {}".format( data_dict, e)) # PROPERTIES # ========== @property def speech_certainty(self): return self.__speech_certainty @speech_certainty.setter def speech_certainty(self, val): val = abs(float(val)) self.__speech_certainty = val if (0 <= val <= 1) else (val / 100.0) # HELPER # ====== @inlineCallbacks def print_data(self, result): yield self.logger.info('Result received: {}'.format(result))
class EngagementHandler(object): def __init__(self, session): self.logger = logging.getLogger("EngagementHandler") self.session = session self.memory = self.session.service("ALMemory") self.face_service = self.session.service("ALFaceDetection") self.tracker = self.session.service("ALTracker") self.tracker_face_size = 0.1 self.last_time_detected = 0 # log the time self.notification_interval = 1 # seconds # observers self.face_detected_observers = Observable() self.face_subscriber = None # subscribe to face events self.face_events(subscribe=True) # SUBSCRIBE # ========= def face_events(self, subscribe=True): if subscribe: self.face_subscriber = self.memory.subscriber("FaceDetected") self.face_subscriber.signal.connect(self.on_face_detected) self.face_service.subscribe("ESEngagementHandler") else: self.face_subscriber = None self.face_service.unsubscribe("ESEngagementHandler") def on_face_detected(self, value): # self.logger.info("Face detected: {}".format(value)) try: if value is None or value == []: time.sleep(1) else: faces_info = value[1] # skip empty frames if faces_info and len(faces_info) > 0: faces_info.pop() # rec info face_size = 0.0 for f_info in faces_info: tmp_size = max(f_info[0][3], f_info[0][4]) face_size = max(face_size, tmp_size) # > x seconds: notify observers detection_interval = time.time() - self.last_time_detected if detection_interval >= self.notification_interval: self.logger.info( "Detected a face: {} | after {}s".format( face_size, detection_interval)) self.last_time_detected = time.time() self.face_detected_observers.notify_all(face_size) else: time.sleep(1) except Exception as e: self.logger.error( "Error while receiving the detected face: {}".format(e)) def face_detection(self, start=False): # start/close the face stream if start: self.face_service.subscribe("ESEngagementHandler") else: self.face_service.unsubscribe("ESEngagementHandler") def face_tracker(self, start=False): target_name = "Face" if start is True: self.tracker.registerTarget(target_name, self.tracker_face_size) self.tracker.track(target_name) else: self.tracker.stopTracker() self.tracker.unregisterAllTargets()
class TabletHandler(object): def __init__(self, session): self.logger = logging.getLogger("TabletHandler") self.session = session self.tablet_service = self.session.service("ALTabletService") self.memory = self.session.service("ALMemory") # TODO: add a listener to the tablet input stream self.tablet_input_listener = self.memory.subscriber("TabletInput") self.tablet_input_listener.signal.connect(self.on_tablet_input) self.tablet_input_observers = Observable() self.is_listening = False def show_webview(self, url="https://www.google.com", hide=False): try: if hide is True: self.tablet_service.hideWebview() else: # Enable tablet wifi and display the webpage self.tablet_service.enableWifi() self.tablet_service.showWebview(url) except Exception as e: self.logger.error(e) def on_tablet_input(self, value=None): self.logger.info("Tablet Input: {}".format(value)) if not self.is_listening: self.logger.info("Not listening!") elif not value: self.logger.info("No input received!") else: try: self.logger.info("Received Tablet Input: {}".format(value)) self.is_listening = False self.input_stream(start=False) tablet_input = value if type(tablet_input) is bytes: tablet_input = tablet_input.decode('utf-8') url = TabletHandler.create_tablet_url(page_name="index") self.show_webview(url=url) self.tablet_input_observers.notify_all(tablet_input) except Exception as e: self.logger.error( "Error while getting the received tablet input: {}".format( e)) def input_stream(self, start=False): self.logger.info("{} Tablet Input stream.".format( "Starting" if start else "Closing")) self.is_listening = start # TODO: start/close the input stream @staticmethod def create_tablet_url(page_name="index", url_params=None): tablet_settings = config_helper.get_tablet_settings() url = "http://{}/{}{}".format( tablet_settings["ip"], tablet_settings["pages"]["{}".format(page_name)], "" if url_params is None else url_params) return url def set_image(self, image_path="img/help_charger.png", hide=False): # If hide is false, display a local image located in img folder in the root of the robot tablet_settings = config_helper.get_tablet_settings() full_path = "http://{}/{}".format(tablet_settings["ip"], image_path) self.logger.info("Image path: {}".format(full_path)) try: self.tablet_service.hideImage( ) if hide is True else self.tablet_service.showImageNoCache( full_path) except Exception as e: self.logger.error("Error while setting tablet image: {}".format(e)) def configure_wifi(self, security="WPA2", ssid="", key=""): try: self.tablet_service.configureWifi(security, ssid, key) self.logger.debug("Successfully configured the wifi.") except Exception as e: self.logger.error("Error while configuring the wifi! {}".format(e))
class Block(Serializable, Observable): def __init__(self, scene, title="Start", socket_types=[], pos=[], parent=None, icon=None, output_edges=1, bg_color=None): super(Block, self).__init__() Observable.__init__(self) # explicit call to second parent class self.logger = logging.getLogger("Block") self.scene = scene self.parent = parent # any container self.graphics_block = None self.bg_color = bg_color self.icon = icon self.title = title # is also the pattern name self.inputs = [] self.outputs = [] self.socket_spacing = 22 self._init_ui(socket_types, pos, output_edges) # add observers self.editing_observers = Observable() self.settings_observers = Observable() # add editing/settings listeners self.content.editing_icon.clicked.connect( lambda: self.editing_observers.notify_all(event=self)) self.content.settings_icon.clicked.connect( lambda: self.settings_observers.notify_all(event=self)) # add block to the scene self.scene.add_block(self) def _init_ui(self, socket_types, pos, output_edges): self.content = ESBlockContentWidget(block=self) self.graphics_block = ESGraphicsBlock(block=self, bg_color=self.bg_color) self._init_sockets(socket_types, output_edges) if pos is not None and len(pos) == 2: self.set_pos(*pos) def _init_sockets(self, socket_types, output_edges): in_counter = 0 out_counter = 0 for st in socket_types: if st is SocketType.INPUT: self.inputs.append( Socket(block=self, index=in_counter, position=Position.BOTTOM_LEFT, socket_type=SocketType.INPUT)) in_counter += 1 else: self.outputs.append( Socket(block=self, index=out_counter, position=Position.TOP_RIGHT, socket_type=SocketType.OUTPUT, edge_limit=output_edges)) out_counter += 1 def get_socket_position(self, index, position): # set x x, y = 0, 0 # for the left side try: if position in (Position.TOP_RIGHT, Position.BOTTOM_RIGHT, Position.CENTER_RIGHT): x = self.graphics_block.width # set y if position in (Position.CENTER_LEFT, Position.CENTER_RIGHT): y = (self.graphics_block.height / 2) - index * self.socket_spacing elif position in (Position.BOTTOM_LEFT, Position.BOTTOM_RIGHT): # start on bottom y = self.graphics_block.height - ( 2 * self.graphics_block.rounded_edge_size ) - index * self.socket_spacing else: y = self.graphics_block.title_height + self.graphics_block.rounded_edge_size + index * self.socket_spacing except Exception as e: self.logger.debug( "Error while getting the socket position! {}".format(e)) finally: return [x, y] def update_connected_edges(self): for socket in self.inputs + self.outputs: socket.update_edge_positions() def is_connected_to(self, other_block): """ :param other_block: :return: True if two blocks are connected; False otherwise. """ for edge in self.scene.edges: if self in (edge.start_socket.block, edge.end_socket.block) and \ other_block in (edge.start_socket.block, edge.end_socket.block): self.logger.info("{} is connected to {}".format( self, other_block)) return True return False def get_edges(self, socket_type=SocketType.OUTPUT): edges = [] for s in (self.outputs if socket_type is SocketType.OUTPUT else self.inputs): edges.extend([e for e in s.edges]) return edges def get_connected_blocks(self, socket_type=SocketType.OUTPUT): blocks = [] # go through target sockets and get the connected blocks for output_socket in (self.outputs if socket_type is SocketType.OUTPUT else self.inputs): connected_sockets = output_socket.get_connected_sockets() if connected_sockets is not None: blocks.extend([s.block for s in connected_sockets]) self.logger.debug("# of Connected blocks: {}".format( 0 if blocks is None else len(blocks))) return blocks def remove(self): # remove socket edges for socket in (self.inputs + self.outputs): # remove edges, if any socket.disconnect_all_edges() # remove block from scene self.scene.remove_block(self) self.graphics_block = None def __str__(self): return "<Block id {}..{}>".format((hex(id(self))[2:5]), (hex(id(self))[-3:])) def get_pos(self): return self.graphics_block.pos() # QPointF def set_pos(self, x, y): self.graphics_block.setPos(x, y) @property def title(self): return self.__title @title.setter def title(self, value): self.__title = value if self.graphics_block is not None: self.graphics_block.title = self.title @property def icon(self): return self.__icon @icon.setter def icon(self, value): self.__icon = value if self.graphics_block is not None: self.graphics_block.set_title_pixmap(self.icon) @property def description(self): return self.content.description @description.setter def description(self, desc): self.content.description = desc def set_selected(self, val): if val is not None and self.graphics_block is not None: self.graphics_block.setSelected(val) @property def pattern(self): return self.parent.pattern if self.parent is not None else self.title ### # SERIALIZATION ### def serialize(self): return OrderedDict([ ("id", self.id), ("title", self.title), ("icon", self.icon), ("pos_x", self.graphics_block.scenePos().x()), ("pos_y", self.graphics_block.scenePos().y()), ("inputs", [s.serialize() for s in self.inputs]), ("outputs", [s.serialize() for s in self.outputs]), ("content", self.content.serialize()), ("parent", {} if self.parent is None else self.parent.serialize()), ("bg_color", self.bg_color) ]) def deserialize(self, data, hashmap={}): self.id = data["id"] hashmap[data["id"]] = self self.icon = data["icon"] self.title = data["title"] self.set_pos(data["pos_x"], data["pos_y"]) self.bg_color = data["bg_color"] if "bg_color" in data.keys() else None # set inputs and outputs data["inputs"].sort( key=lambda s: s["index"] + Position[s["position"]].value * 1000) data["outputs"].sort( key=lambda s: s["index"] + Position[s["position"]].value * 1000) self.inputs = [] for s_data in data["inputs"]: socket = Socket(block=self, index=s_data["index"], position=Position[s_data["position"]], socket_type=SocketType[s_data["socket_type"]], edge_limit=s_data["edge_limit"] if "edge_limit" in s_data.keys() else 1) socket.deserialize(s_data, hashmap) self.inputs.append(socket) self.outputs = [] for s_data in data["outputs"]: socket = Socket(block=self, index=s_data["index"], position=Position[s_data["position"]], socket_type=SocketType[s_data["socket_type"]], edge_limit=s_data["edge_limit"] if "edge_limit" in s_data.keys() else 1) socket.deserialize(s_data, hashmap) self.outputs.append(socket) self.content.deserialize(data["content"], hashmap) # set parent if "parent" in data.keys() and len(data["parent"]) > 0: self.parent = block_helper.create_block_parent( data["parent"], hashmap) return True
class TabletHandler(object): def __init__(self, session): self.logger = logging.getLogger("TabletHandler") self.session = session # add a listener to the tablet input stream self.session.subscribe(self.on_tablet_input, "rom.optional.tablet_input.stream") self.tablet_input_observers = Observable() self.is_listening = False def show_webview(self, url="https://www.google.com", hide=False): try: if hide is True: self.session.call("rom.optional.tablet.stop") else: self.session.call("rom.optional.tablet.view", url=url) except Exception as e: self.logger.error(e) @inlineCallbacks def on_tablet_input(self, frame=None): if (not self.is_listening) or (frame is None or len(frame) == 0): self.logger.info("Not listening or no frames!") else: try: self.logger.info("Received Tablet Input: {}".format( frame["data"])) self.is_listening = False yield self.input_stream(start=False) tablet_input = frame["data"]["body"]["text"] if type(tablet_input) is bytes: tablet_input = tablet_input.decode('utf-8') url = TabletHandler.create_tablet_url(page_name="index") yield self.show_webview(url=url) self.tablet_input_observers.notify_all(tablet_input) except Exception as e: self.logger.error( "Error while getting the received tablet input: {}".format( e)) def input_stream(self, start=False): self.logger.info("{} Tablet Input stream.".format( "Starting" if start else "Closing")) self.is_listening = start # start/close the input stream self.session.call("rom.optional.tablet_input.stream" if start else "rom.optional.tablet_input.close") @staticmethod def create_tablet_url(page_name="index", url_params=None): tablet_settings = config_helper.get_tablet_settings() url = "http://{}/{}{}".format( tablet_settings["ip"], tablet_settings["pages"]["{}".format(page_name)], "" if url_params is None else url_params) return url def set_image(self, image_path="img/help_charger.png", hide=False): try: self.session.call("rom.optional.tablet.image", location=image_path, hide=hide) except Exception as e: self.logger.error("Error while setting tablet image: {}".format(e)) def configure_wifi(self, security="WPA2", ssid="", key=""): try: self.session.call("rom.optional.tablet.wifi", security=security, ssid=ssid, key=key) self.logger.debug("Successfully configured the wifi.") except Exception as e: self.logger.error("Error while configuring the wifi! {}".format(e))
class InteractionController(object): def __init__(self, block_controller, music_controller=None): self.logger = logging.getLogger("Interaction Controller") self.block_controller = block_controller self.music_controller = music_controller self.robot_controller = None self.wakeup_thread = None self.animation_thread = None self.engagement_thread = None self.face_tracker_thread = None self.timer_helper = TimerHelper() self.music_command = None self.animations_lst = [] self.animation_time = 0 self.animation_counter = -1 self.robot_volume = 50 self.robot_ip = None self.port = None self.robot_name = None self.engagement_counter = 0 self.is_ready_to_interact = False self.current_interaction_block = None self.previous_interaction_block = None self.interaction_blocks = None self.interaction_design = None self.stop_playing = False self.execution_result = None self.has_finished_playing_observable = Observable() def connect_to_robot(self, robot_ip, port, robot_name=None): self.robot_ip = robot_ip self.port = port self.robot_name = robot_name pconfig.robot_ip = self.robot_ip pconfig.naoqi_port = self.port self.robot_controller = RobotController() message, error, is_awake = self.robot_controller.connect(robot_ip=self.robot_ip, port=self.port, robot_name=self.robot_name) self.update_threads() return message, error, is_awake def disconnect_from_robot(self): self.engagement_counter = 0 try: if self.animation_thread is not None: self.engagement(start=False) except Exception as e: self.logger.error("Error while disconnecting from robot. | {}".format(e)) finally: self.robot_controller = None # if self.animation_thread is not None: # self.animation_thread.dialog(start=False) return True def is_connected(self): return False if self.robot_controller is None else True def update_threads(self, enable_moving=False): if self.animation_thread is None: self.animation_thread = AnimateRobotThread(robot_ip=self.robot_ip, port=self.port, robot_name=self.robot_name) self.animation_thread.dialog_started.connect(self.engagement) self.animation_thread.customized_say_completed.connect(self.customized_say) self.animation_thread.animation_completed.connect(self.on_animation_completed) # TODO: check enable moving from the ui, e.g., self.ui.enableMovingCheckBox.isChecked() self.animation_thread.moving_enabled = enable_moving self.animation_thread.is_disconnected.connect(self.disconnect_from_robot) else: self.animation_thread.connect_to_robot(robot_ip=self.robot_ip, port=self.port, robot_name=self.robot_name) if self.face_tracker_thread is None: self.face_tracker_thread = FaceTrackerThread(robot_controller=self.robot_controller) self.face_tracker_thread.is_disconnected.connect(self.disconnect_from_robot) else: # update the robot controller self.face_tracker_thread.robot_controller = self.robot_controller if self.engagement_thread is None: self.engagement_thread = EngagementThread(robot_controller=self.robot_controller) self.engagement_thread.is_engaged.connect(lambda: self.interaction(start=True)) self.engagement_thread.is_disconnected.connect(self.disconnect_from_robot) else: # update the robot controller self.engagement_thread.robot_controller = self.robot_controller def wakeup_robot(self): success = False try: self.wakeup_thread = WakeUpRobotThread(robot_controller=self.robot_controller) self.wakeup_thread.start() success = True except Exception as e: self.logger.error("Error while waking up the robot! | {}".format(e)) finally: return success def rest_robot(self): return self.robot_controller.posture(wakeup=False) # TOUCH # ------ def enable_touch(self): self.robot_controller.touch() # TRACKING # --------- def tracking(self, enable=True): self.robot_controller.tracking(enable=enable) # BEHAVIORS # --------- def animate(self, animation_name=None): self.animation_thread.animate(animation_name=animation_name) def animated_say(self, message=None, animation_name=None): self.animation_thread.animated_say(message=message, animation_name=animation_name) # SPEECH # ------ def say(self, message=None): to_say = "Hello!" if message is None else message if message is None: self.logger.info(to_say) self.animated_say(message=to_say) def start_playing(self, int_block, engagement_counter=0): if int_block is None: return False self.stop_playing = False self.previous_interaction_block = None self.current_interaction_block = int_block self.current_interaction_block.execution_mode = ExecutionMode.NEW self.logger.debug("Started playing the blocks") # set the engagement counter self.engagement_counter = int(engagement_counter) # int(self.ui.enagementRepetitionsSpinBox.value()) # ready to interact self.is_ready_to_interact = True # self.animation_thread.robot_controller.posture(reset = True) if self.animation_thread.dialog_thread is None or (not self.animation_thread.dialog_thread.isRunning()): self.animation_thread.dialog(start=True) self.logger.info("Called dialog to start!") # start engagement self.engagement(start=True) return True def get_next_interaction_block(self): if self.current_interaction_block is None: return None next_block = None connecting_edge = None self.logger.debug("Getting the next interaction block...") try: self.logger.debug("Execution Result: {}".format(self.animation_thread.execution_result)) next_block, connecting_edge = self.current_interaction_block.get_next_interaction_block( execution_result=self.animation_thread.execution_result) # complete execution self.current_interaction_block.execution_mode = ExecutionMode.COMPLETED # update previous block self.previous_interaction_block = self.current_interaction_block except Exception as e: self.logger.error("Error while getting the next block! {}".format(e)) finally: return next_block, connecting_edge def interaction(self, start): self.logger.info("Interaction called with start = {}".format(start)) if start is False: # stop the interaction self.tablet_image(hide=True) self.robot_controller.is_interacting(False) self.is_ready_to_interact = False self.interaction_blocks = [] # empty the blocks self.engagement(start=False) return "Successfully stopped the interaction" elif self.is_ready_to_interact is True: # start is True self.robot_controller.is_interacting(start) self.customized_say() # start interacting def stop_engagement_callback(self): # stop! self.engagement_counter = 0 self.interaction(start=False) def engagement(self, start): """ @param start = bool """ self.logger.info("Engagement called with start = {} and counter = {}".format(start, self.engagement_counter)) if start is True: self.engagement_thread.engagement(start=True) self.face_tracker_thread.track(start=True) else: # decrease the engagement counter self.engagement_counter -= 1 # stop the engagement if the counter is <= 0 if self.engagement_counter <= 0: self.animation_thread.dialog(start=False) self.engagement_thread.engagement(start=False) self.face_tracker_thread.track(start=False) self.has_finished_playing_observable.notify_all(True) # TODO: move to ui # self._enable_buttons([self.ui.actionPlay], enabled=True) # self._enable_buttons([self.ui.actionStop], enabled=False) else: # continue self.animation_thread.dialog(start=False, pause=True) # ready to interact self.is_ready_to_interact = True def verify_current_interaction_block(self): # if there are no more blocks, stop interacting if self.current_interaction_block is None or self.stop_playing is True: self.animation_thread.customized_say(reset=True) # stop interacting self.interaction(start=False) self.tablet_image(hide=True) return False return True def customized_say(self): if self.verify_current_interaction_block() is False: return False if self.animation_thread.isRunning(): self.logger.debug("*** Animation Thread is still running!") return QTimer.singleShot(1000, self.customized_say) # wait for the thread to finish self.block_controller.clear_selection() connecting_edge = None # check for remaining actions if self.execute_action_command() is True: return True if self.previous_interaction_block is None: # interaction has just started self.previous_interaction_block = self.current_interaction_block else: # get the next block to say self.current_interaction_block, connecting_edge = self.get_next_interaction_block() if self.verify_current_interaction_block() is False: return False # execute the block self.current_interaction_block.execution_mode = ExecutionMode.EXECUTING self.current_interaction_block.set_selected(True) self.current_interaction_block.volume = self.robot_volume if connecting_edge is not None: connecting_edge.set_selected(True) # TODO: set the block state to 'executing' # set the tracker's gaze pattern if not self.face_tracker_thread.isRunning(): self.face_tracker_thread.track() self.face_tracker_thread.gaze_pattern = self.current_interaction_block.behavioral_parameters.gaze_pattern # get the result from the execution self.animation_thread.customized_say(interaction_block=self.current_interaction_block) self.logger.debug("Robot: {}".format(self.current_interaction_block.message)) return True def execute_action_command(self): # check for remaining actions if self.current_interaction_block.execution_mode is ExecutionMode.EXECUTING: if self.current_interaction_block.has_action(action_type=ActionCommand.PLAY_MUSIC): self.on_music_mode() return True elif self.current_interaction_block.has_action(action_type=ActionCommand.WAIT): self.on_wait_mode() return True return False def on_wait_mode(self): wait_time = 1 # 1s try: if self.current_interaction_block is not None: self.current_interaction_block.execution_mode = ExecutionMode.COMPLETED wait_command = self.current_interaction_block.action_command if wait_command is not None: wait_time = wait_command.wait_time except Exception as e: self.logger.error("Error while setting wait time! {}".format(e)) finally: self.logger.debug("Waiting for {} s".format(wait_time)) QTimer.singleShot(wait_time * 1000, self.customized_say) def on_music_mode(self): if self.music_controller is None: self.logger.debug("Music player is not connected! Will skip playing music.") self.on_music_stop() else: self.current_interaction_block.action_command.music_controller = self.music_controller success = self.current_interaction_block.action_command.execute() if success is True: self.logger.debug("Playing now: {}".format(self.current_interaction_block.action_command.track)) # TODO: specify wait time as track time when play_time is < 0 # use action play time wait_time = self.current_interaction_block.action_command.play_time if wait_time <= 0: wait_time = 30 # wait for 30s then continue anim_key = self.current_interaction_block.action_command.animations_key if anim_key is None or anim_key == "": QTimer.singleShot(int(wait_time) * 1000, self.on_music_stop) else: self.on_animation_mode(music_command=self.current_interaction_block.action_command, animation_time=int(wait_time)) # QTimer.singleShot(wait_time * 1000, self.on_music_stop) else: self.logger.warning("Unable to play music! {}".format(self.music_controller.warning_message)) self.on_music_stop() def on_animation_mode(self, music_command, animation_time=0): self.music_command = music_command self.animations_lst = config_helper.get_animations()[music_command.animations_key] self.animation_time = animation_time self.animation_counter = -1 self.timer_helper.start() self.execute_next_animation() def on_animation_completed(self): if self.animation_thread.isRunning(): self.logger.debug("*** Animation Thread is still running!") QTimer.singleShot(2000, self.on_animation_completed) # wait for the thread to finish else: QTimer.singleShot(3000, self.execute_next_animation) def execute_next_animation(self): if self.music_command is None or len(self.animations_lst) == 0: QTimer.singleShot(1000, self.on_music_stop) elif self.timer_helper.elapsed_time() <= self.animation_time - 4: # use 4s threshold # repeat the animations if the counter reached the end of the lst self.animation_counter += 1 if self.animation_counter >= len(self.animations_lst): self.animation_counter = 0 animation, message = self.get_next_animation(self.animation_counter) if message is None or message == "": self.animation_thread.animate(animation_name=animation) else: self.animation_thread.animated_say(message=message, animation_name=animation, robot_voice=self.get_robot_voice()) else: remaining_time = self.animation_time - self.timer_helper.elapsed_time() QTimer.singleShot(1000 if remaining_time < 0 else remaining_time * 1000, self.on_music_stop) def get_next_animation(self, anim_index): anim, msg = ("", "") try: animation_dict = self.animations_lst[anim_index] if len(animation_dict) > 0: anim = animation_dict.keys()[0] msg = animation_dict[anim] except Exception as e: self.logger.error("Error while getting next animation! {}".format(e)) finally: return anim, msg def get_robot_voice(self): if self.current_interaction_block is None: return None return self.current_interaction_block.behavioral_parameters.voice def on_music_stop(self): self.logger.debug("Finished playing music.") try: if self.current_interaction_block is not None: self.current_interaction_block.execution_mode = ExecutionMode.COMPLETED if self.music_controller is not None: self.music_controller.pause() except Exception as e: self.logger.error("Error while stopping the music! {}".format(e)) finally: self.customized_say() def test_behavioral_parameters(self, interaction_block, behavioral_parameters, volume): message, error = (None,) * 2 if self.robot_controller is None: error = "Please connect to the robot to be able to test the parameters." else: b = interaction_block.clone() b.behavioral_parameters = behavioral_parameters b.behavioral_parameters.speech_act = interaction_block.speech_act.clone() b.behavioral_parameters.voice.volume = volume self.face_tracker_thread.gaze_pattern = b.behavioral_parameters.gaze_pattern self.animation_thread.test_mode = True self.animation_thread.customized_say(interaction_block=b) message = "Testing: {}".format(b.message) return message, error # TABLET # ------ def tablet_image(self, hide=False): if self.robot_controller is not None: self.robot_controller.tablet_image(hide=hide) # MOVEMENT # -------- def enable_moving(self): if self.animation_thread is None: return self.animation_thread.moving_enabled = self.ui.enableMovingCheckBox.isChecked() self.logger.info("#### MOVING: {}".format(self.animation_thread.moving_enabled))