async def session_command_execute_handler( session_id: route_models.IdentifierType, command_request: route_models.CommandRequest, session_manager: SessionManager = Depends(get_session_manager), ) -> route_models.CommandResponse: """ Execute a session command """ session_obj = get_session(manager=session_manager, session_id=session_id, api_router=router) if not session_manager.is_active(session_obj.meta.identifier): raise RobotServerError( status_code=http_status_codes.HTTP_403_FORBIDDEN, error=Error( title=f"Session '{session_id}' is not active", detail="Only the active session can execute commands", )) try: command = create_command(command_request.data.attributes.command, command_request.data.attributes.data) command_result = await session_obj.command_executor.execute(command) log.debug(f"Command completed {command}") # TODO: Amit 07/30/2020 - SessionCommandException should be a # RobotServerError. Customized by raiser with status code and rest of # Error attributes. The job here would be to log and re-raise. except CommandExecutionConflict as e: log.exception("Failed to execute command due to conflict") raise RobotServerError(status_code=http_status_codes.HTTP_409_CONFLICT, error=Error( title="Command execution conflict", detail=str(e), )) except SessionCommandException as e: log.exception("Failed to execute command") raise RobotServerError( status_code=http_status_codes.HTTP_400_BAD_REQUEST, error=Error( title="Command execution error", detail=str(e), )) return route_models.CommandResponse(data=ResponseDataModel.create( attributes=route_models.SessionCommand( data=command_result.content.data, command=command_result.content.name, status=command_result.result.status, createdAt=command_result.meta.created_at, startedAt=command_result.result.started_at, completedAt=command_result.result.completed_at), resource_id=command_result.meta.identifier), links=get_valid_session_links( session_id, router))
async def create_session_handler( create_request: route_models.SessionCreateRequest, session_manager: SessionManager = Depends(get_session_manager)) \ -> route_models.SessionResponse: """Create a session""" session_type = create_request.data.attributes.sessionType create_params = create_request.data.attributes.createParams try: new_session = await session_manager.add( session_type=session_type, session_meta_data=SessionMetaData(create_params=create_params)) # TODO: Amit 07/30/2020 - SessionCreationException should be a # RobotServerError. Customized by raiser with status code and rest of # Error attributes. The job here would be to log and re-raise. except SessionCreationException as e: log.exception("Failed to create session") raise RobotServerError( status_code=http_status_codes.HTTP_400_BAD_REQUEST, error=Error( title="Creation Failed", detail=f"Failed to create session of type " f"'{session_type}': {str(e)}.", )) return route_models.SessionResponse(data=ResponseDataModel.create( attributes=new_session.get_response_model(), resource_id=new_session.meta.identifier), links=get_valid_session_links( new_session.meta.identifier, router))
async def delete_session_handler( session_id: route_models.IdentifierType, session_manager: SessionManager = Depends(get_session_manager)) \ -> route_models.SessionResponse: """Delete a session""" session_obj = get_session(manager=session_manager, session_id=session_id, api_router=router) try: await session_manager.remove(session_obj.meta.identifier) # TODO: Amit 07/30/2020 - SessionException should be a RobotServerError. # Customized by raiser with status code and rest of # Error attributes. The job here would be to log and re-raise. except SessionException as e: log.exception("Failed to remove a session session") raise RobotServerError( status_code=http_status_codes.HTTP_400_BAD_REQUEST, error=Error( title="Removal Failed", detail=f"Failed to remove session " f"'{session_id}': {str(e)}.", )) return route_models.SessionResponse( data=ResponseDataModel.create( attributes=session_obj.get_response_model(), resource_id=session_id), links={ "POST": ResourceLink( href=router.url_path_for(create_session_handler.__name__)), })
def __init__(self, hardware: ThreadManager, mount: Mount, has_calibration_block: bool, tip_rack: 'LabwareDefinition'): self._hardware = hardware self._mount = mount self._has_calibration_block = has_calibration_block self._hw_pipette = self._hardware._attached_instruments[mount] if not self._hw_pipette: raise RobotServerError( definition=CalibrationError.NO_PIPETTE_ON_MOUNT, mount=mount) self._tip_origin_pt: Optional[Point] = None self._nozzle_height_at_reference: Optional[float] = None deck_load_name = SHORT_TRASH_DECK if ff.short_fixed_trash() \ else STANDARD_DECK self._deck = deck.Deck(load_name=deck_load_name) self._tip_rack = self._get_tip_rack_lw(tip_rack) self._initialize_deck() self._current_state = State.sessionStarted self._state_machine = TipCalibrationStateMachine() self._command_map: COMMAND_MAP = { CalibrationCommand.load_labware: self.load_labware, CalibrationCommand.jog: self.jog, CalibrationCommand.pick_up_tip: self.pick_up_tip, CalibrationCommand.invalidate_tip: self.invalidate_tip, CalibrationCommand.save_offset: self.save_offset, TipLengthCalibrationCommand.move_to_reference_point: self.move_to_reference_point, # noqa: E501 CalibrationCommand.move_to_tip_rack: self.move_to_tip_rack, # noqa: E501 CalibrationCommand.exit: self.exit_session, }
def __init__(self, hardware: ThreadManager, mount: Mount = Mount.RIGHT): self._hardware = hardware self._mount = mount self._hw_pipette = self._hardware._attached_instruments[mount] if not self._hw_pipette: raise RobotServerError( definition=CalibrationError.NO_PIPETTE_ON_MOUNT, mount=mount) deck_load_name = SHORT_TRASH_DECK if ff.short_fixed_trash() \ else STANDARD_DECK self._deck = deck.Deck(load_name=deck_load_name) self._tip_rack = self._get_tip_rack_lw() self._deck[TIP_RACK_SLOT] = self._tip_rack self._current_state = State.sessionStarted self._state_machine = PipetteOffsetCalibrationStateMachine() point_one_pos = \ self._deck.get_calibration_position(POINT_ONE_ID).position self._cal_ref_point = Point(*point_one_pos) self._tip_origin_pt: Optional[Point] = None self._command_map: COMMAND_MAP = { CalibrationCommand.load_labware: self.load_labware, CalibrationCommand.jog: self.jog, CalibrationCommand.pick_up_tip: self.pick_up_tip, CalibrationCommand.invalidate_tip: self.invalidate_tip, CalibrationCommand.save_offset: self.save_offset, CalibrationCommand.move_to_tip_rack: self.move_to_tip_rack, CalibrationCommand.move_to_deck: self.move_to_deck, CalibrationCommand.move_to_point_one: self.move_to_point_one, CalibrationCommand.exit: self.exit_session, }
async def delete_specific_labware_calibration( calibrationId: cal_types.CalibrationID): try: delete.delete_offset_file(calibrationId) except (FileNotFoundError, KeyError): error = Error(title='{calibrationId} does not exist.') raise RobotServerError(status_code=status.HTTP_404_NOT_FOUND, error=error)
def _get_tip_rack_lw(self, tip_rack_def: 'LabwareDefinition') -> labware.Labware: try: return labware.load_from_definition( tip_rack_def, self._deck.position_for(TIP_RACK_SLOT)) except Exception: raise RobotServerError(definition=CalibrationError.BAD_LABWARE_DEF)
async def get_specific_pipette_offset_calibration(pipette_id: str, mount: pip_models.MountType): try: delete.delete_pipette_offset_file(pipette_id, ot_types.Mount[mount.upper()]) except FileNotFoundError: raise RobotServerError(definition=CommonErrorDef.RESOURCE_NOT_FOUND, resource='PipetteOffsetCalibration', id=f"{pipette_id}&{mount}")
async def create_session_handler( create_request: SessionCreateRequest, session_manager: SessionManager = Depends(get_session_manager), hardware=Depends(get_hardware)) \ -> session.SessionResponse: """Create a session""" session_type = create_request.data.attributes.sessionType # TODO We use type as ID while we only support one session type. session_id = session_type.value current_session = session_manager.sessions.get(session_id) if not current_session: try: # TODO generalize for other kinds of sessions new_session = await CheckCalibrationSession.build(hardware) except (AssertionError, CalibrationException) as e: raise RobotServerError( status_code=http_status_codes.HTTP_400_BAD_REQUEST, error=Error( title="Creation Failed", detail=f"Failed to create session of type " f"'{session_type}': {str(e)}.", )) session_manager.sessions[session_id] = new_session return session.SessionResponse( data=ResponseDataModel.create(attributes=session.Session( sessionType=session_type, details=create_session_details(new_session)), resource_id=session_id), links=get_valid_session_links(session_id, router)) else: raise RobotServerError( status_code=http_status_codes.HTTP_409_CONFLICT, error=Error( title="Conflict", detail=f"A session with id '{session_id}' already exists. " f"Please delete to proceed.", links={ "DELETE": router.url_path_for(delete_session_handler.__name__, session_id=session_id) }))
def get_session(manager: SessionManager, session_id: IdentifierType, api_router: APIRouter) -> BaseSession: """Get the session or raise a RobotServerError""" found_session = manager.get_by_id(session_id) if not found_session: # There is no session raise error raise RobotServerError(definition=CommonErrorDef.RESOURCE_NOT_FOUND, links=get_sessions_links(api_router), resource='session', id=session_id) return found_session
async def session_command_execute_handler( session_id: route_models.IdentifierType, command_request: route_models.CommandRequest, session_manager: SessionManager = Depends(get_session_manager), ) -> route_models.CommandResponse: """ Execute a session command """ session_obj = get_session(manager=session_manager, session_id=session_id, api_router=router) if not session_manager.is_active(session_obj.meta.identifier): raise RobotServerError( status_code=http_status_codes.HTTP_403_FORBIDDEN, error=Error( title=f"Session '{session_id}' is not active", detail="Only the active session can execute commands", )) try: command = create_command(command_request.data.attributes.command, command_request.data.attributes.data) command_result = await session_obj.command_executor.execute(command) log.debug(f"Command completed {command}") except SessionCommandException as e: log.exception("Failed to execute command") raise RobotServerError( status_code=http_status_codes.HTTP_400_BAD_REQUEST, error=Error( title="Command execution error", detail=str(e), )) return route_models.CommandResponse(data=ResponseDataModel.create( attributes=route_models.SessionCommand( data=command_result.content.data, command=command_result.content.name, status=command_result.result.status), resource_id=command_result.meta.identifier), links=get_valid_session_links( session_id, router))
async def get_specific_labware_calibration( calibrationId: str) -> lw_models.SingleCalibrationResponse: calibration: Optional[cal_types.CalibrationInformation] = None for cal in get_cal.get_all_calibrations(): if calibrationId == cal.labware_id: calibration = cal break if not calibration: error = Error(title='{calibrationId} does not exist.') raise RobotServerError(status_code=status.HTTP_404_NOT_FOUND, error=error) formatted_calibrations = _format_calibrations([calibration]) return lw_models.SingleCalibrationResponse( data=formatted_calibrations[0])
def get_session(manager: SessionManager, session_id: str, api_router: APIRouter) -> CalibrationSession: """Get the session or raise a RobotServerError""" found_session = manager.sessions.get(session_id) if not found_session: # There is no session raise error raise RobotServerError( status_code=http_status_codes.HTTP_404_NOT_FOUND, error=Error(title="No session", detail=f"Cannot find session with id '{session_id}'.", links={ "POST": api_router.url_path_for( create_session_handler.__name__) })) return found_session
def _look_up_state(self) -> CalibrationCheckState: """ We want to check whether a tip pick up was dangerous during the tip inspection state, but the reference points are actually saved during the preparing pipette state, so we should reference those states when looking up the reference point. :return: The calibration check state that the reference point was saved under for tip pick up. """ if self.current_state_name == CalibrationCheckState.inspectingFirstTip: return CalibrationCheckState.preparingFirstPipette elif self.current_state_name == \ CalibrationCheckState.inspectingSecondTip: return CalibrationCheckState.preparingSecondPipette else: raise RobotServerError( definition=CalibrationError.NO_STATE_TRANSITION, state=self.current_state_name)
async def add( self, session_type: SessionType, session_meta_data: SessionMetaData, ) -> BaseSession: """Add a new session""" cls = SessionTypeToClass.get(session_type) if not cls: raise SessionCreationException("Session type is not supported") session = await cls.create(configuration=self._session_common, instance_meta=session_meta_data) if session.meta.identifier in self._sessions: raise RobotServerError( definition=CommonErrorDef.RESOURCE_ALREADY_EXISTS, resource="session", id=session.meta.identifier) self._sessions[session.meta.identifier] = session self._active.active_id = session.meta.identifier log.debug(f"Added new session: {session}") return session
async def session_command_create_handler( session_id: str, command_request: session.CommandRequest, session_manager: SessionManager = Depends(get_session_manager), ) -> session.CommandResponse: """ Process a session command """ session_obj = typing.cast( CheckCalibrationSession, get_session(manager=session_manager, session_id=session_id, api_router=router)) command = command_request.data.attributes.command command_data = command_request.data.attributes.data try: await session_obj.trigger_transition( trigger=command.value, **(command_data.dict() if command_data else {})) except (AssertionError, StateMachineError) as e: raise RobotServerError( status_code=http_status_codes.HTTP_400_BAD_REQUEST, error=Error( title="Exception", detail=str(e), )) return session.CommandResponse( data=ResponseDataModel.create( attributes=session.SessionCommand(data=command_data, command=command, status='accepted'), # TODO have session create id for command for later querying resource_id=str(uuid4())), meta=session.Session( details=create_session_details(session_obj), # TODO Get type from session sessionType=models.SessionType.calibration_check), links=get_valid_session_links(session_id, router))
async def create_session_handler( create_request: route_models.SessionCreateRequest, session_manager: SessionManager = Depends(get_session_manager)) \ -> route_models.SessionResponse: """Create a session""" session_type = create_request.data.attributes.sessionType try: new_session = await session_manager.add(session_type) except SessionCreationException as e: log.exception("Failed to create session") raise RobotServerError( status_code=http_status_codes.HTTP_400_BAD_REQUEST, error=Error( title="Creation Failed", detail=f"Failed to create session of type " f"'{session_type}': {str(e)}.", )) return route_models.SessionResponse(data=ResponseDataModel.create( attributes=new_session.get_response_model(), resource_id=new_session.meta.identifier), links=get_valid_session_links( new_session.meta.identifier, router))
def _select_target_pipette(self) -> Tuple[Pipette, Mount]: """ Select pipette for calibration based on: 1: smaller max volume 2: single-channel over multi 3: right mount over left """ if not any(self._hardware._attached_instruments.values()): raise RobotServerError( definition=CalibrationError.NO_PIPETTE_ATTACHED, flow='Deck Calibration') pips = {m: p for m, p in self._hardware._attached_instruments.items() if p} if len(pips) == 1: for mount, pip in pips.items(): return pip, mount right_pip = pips[Mount.RIGHT] left_pip = pips[Mount.LEFT] if right_pip.config.max_volume > left_pip.config.max_volume or \ right_pip.config.channels > left_pip.config.channels: return left_pip, Mount.LEFT else: return right_pip, Mount.RIGHT
def _get_pip_info_by_mount( new_pipettes: typing.Dict[Mount, Pipette.DictType]) \ -> typing.Dict[Mount, PipetteInfo]: pip_info_by_mount = {} attached_pips = {m: p for m, p in new_pipettes.items() if p} num_pips = len(attached_pips) if num_pips > 0: for mount, data in attached_pips.items(): if data: rank = PipetteRank.first if num_pips == 2 and mount == Mount.LEFT: rank = PipetteRank.second cp = None if data['channels'] == 8: cp = CriticalPoint.FRONT_NOZZLE pip_info_by_mount[mount] = PipetteInfo(tiprack_id=None, critical_point=cp, rank=rank, mount=mount) return pip_info_by_mount else: raise RobotServerError( definition=CalibrationError.NO_PIPETTE_ATTACHED, flow='calibration check')
async def delete_access_token(access_token: access.TokenType) \ -> access.AccessTokenResponse: raise RobotServerError(status_code=status_codes.HTTP_501_NOT_IMPLEMENTED, error=Error(title="Not implemented"))
async def post_control(scope: access.ControlScope, access_token: access.AccessTokenRequest) \ -> access.AccessTokenResponse: raise RobotServerError(definition=CommonErrorDef.NOT_IMPLEMENTED)
async def put_control(scope: access.ControlScope, token: access.TokenType) \ -> access.AccessTokenResponse: raise RobotServerError(status_code=status_codes.HTTP_501_NOT_IMPLEMENTED, error=Error(title="Not implemented"))
async def get_access_tokens() -> access.MultipleAccessTokenResponse: raise RobotServerError(definition=CommonErrorDef.NOT_IMPLEMENTED)
async def delete_access_token(access_token: access.TokenType) \ -> access.AccessTokenResponse: raise RobotServerError(definition=CommonErrorDef.NOT_IMPLEMENTED)
async def get_access_tokens() -> access.MultipleAccessTokenResponse: raise RobotServerError(status_code=status_codes.HTTP_501_NOT_IMPLEMENTED, error=Error(title="Not implemented"))
async def release_control(scope: access.ControlScope, token: access.TokenType) \ -> access.AccessTokenResponse: raise RobotServerError(definition=CommonErrorDef.NOT_IMPLEMENTED)