def MoveToPosition(self, request, context: grpc.ServicerContext) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Executes the observable command "Move To Position"
            Move the axis to the given position with a certain velocity

        :param request: gRPC request containing the parameters passed:
            request.Position (Position): The position to move to. Has to be in the range between MinimumPosition and MaximumPosition. See PositionUnit for the unit that is used for a specific axis. E.g. for rotational axis systems one of the axes needs a position specified in radians.
            request.Velocity (Velocity): The velocity value for the movement. Has to be in the range between MinimumVelocity and MaximumVelocity.
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A command confirmation object with the following information:
            commandId: A command id with which this observable command can be referenced in future calls
            lifetimeOfExecution: The (maximum) lifetime of this command call.
        """

        # initialise default values
        #: Duration silaFW_pb2.Duration(seconds=<seconds>, nanos=<nanos>)
        lifetime_of_execution: silaFW_pb2.Duration = None

        # TODO:
        #   Execute the actual command
        #   Optional: Generate a lifetime_of_execution

        # respond with UUID and lifetime of execution
        command_uuid = silaFW_pb2.CommandExecutionUUID(value=str(uuid.uuid4()))
        if lifetime_of_execution is not None:
            return silaFW_pb2.CommandConfirmation(
                commandExecutionUUID=command_uuid,
                lifetimeOfExecution=lifetime_of_execution)
        else:
            return silaFW_pb2.CommandConfirmation(
                commandExecutionUUID=command_uuid)
Beispiel #2
0
    def Subscribe_FlowUnit(self, request, context: grpc.ServicerContext) \
            -> PumpUnitController_pb2.Subscribe_FlowUnit_Responses:
        """
        Requests the observable property Flow Unit
            The currently used flow unit.

        :param request: An empty gRPC request object (properties have no parameters)
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A response object with the following fields:
            FlowUnit (Flow Unit): The currently used flow unit.
        """
        new_volume_unit, new_time_unit = uc.flow_unit_to_string(
            self.pump.get_flow_unit()).split('/')
        volume_unit, time_unit = "", ""  # force sending the first value
        while not self.system.state.shutting_down():
            if self.system.state.is_operational():
                new_volume_unit, new_time_unit = uc.flow_unit_to_string(
                    self.pump.get_flow_unit()).split('/')
            if new_volume_unit != volume_unit or new_time_unit != time_unit:
                volume_unit, time_unit = new_volume_unit, new_time_unit
                yield PumpUnitController_pb2.Subscribe_FlowUnit_Responses(
                    FlowUnit=PumpUnitController_pb2.
                    Subscribe_FlowUnit_Responses.FlowUnit_Struct(
                        VolumeUnit=PumpUnitController_pb2.DataType_VolumeUnit(
                            VolumeUnit=silaFW_pb2.String(value=volume_unit)),
                        TimeUnit=PumpUnitController_pb2.DataType_TimeUnit(
                            TimeUnit=silaFW_pb2.String(value=time_unit))))
            # we add a small delay to give the client a chance to keep up.
            time.sleep(0.1)
    def Shutdown(self, request, context: grpc.ServicerContext) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Executes the observable command "Shutdown"
            Initiates the shutdown routine. If no errors occurred during the shutdown process the server should be considered ready to be physically shutdown (i.e. the device can be shut down/powered off).

        :param request: gRPC request containing the parameters passed:
            request.EmptyParameter (Empty Parameter): An empty parameter data type used if no parameter is required.
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A command confirmation object with the following information:
            commandId: A command id with which this observable command can be referenced in future calls
            lifetimeOfExecution: The (maximum) lifetime of this command call.
        """

        # respond with UUID
        self.command_uuid = str(uuid.uuid4())

        if isinstance(self.device, ContiFlowPump):
            self._save_contiflow_params()
            for i in range(2):
                self._save_drive_position_counters(
                    self.device.get_syringe_pump(i))
        else:
            self._save_drive_position_counters(self.device)

        self._write_config()

        return silaFW_pb2.CommandConfirmation(
            commandExecutionUUID=silaFW_pb2.CommandExecutionUUID(
                value=self.command_uuid))
    def Shutdown_Info(self, request, context: grpc.ServicerContext) \
            -> silaFW_pb2.ExecutionInfo:
        """
        Returns execution information regarding the command call :meth:`~.Shutdown`.

        :param request: A request object with the following properties
            commandId: The UUID of the command executed.
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: An ExecutionInfo response stream for the command with the following fields:
            commandStatus: Status of the command (enumeration)
            progressInfo: Information on the progress of the command (0 to 1)
            estimatedRemainingTime: Estimate of the remaining time required to run the command
            updatedLifetimeOfExecution: An update on the execution lifetime
        """
        # Get the UUID of the command
        command_uuid = request.value

        if not command_uuid or command_uuid != self.command_uuid:
            raise SiLAFrameworkError(error_type=SiLAFrameworkErrorType.
                                     INVALID_COMMAND_EXECUTION_UUID)

        yield silaFW_pb2.ExecutionInfo(
            commandStatus=silaFW_pb2.ExecutionInfo.CommandStatus.running)
        yield silaFW_pb2.ExecutionInfo(commandStatus=silaFW_pb2.ExecutionInfo.
                                       CommandStatus.finishedSuccessfully)
    def Subscribe_Position(self, request, context: grpc.ServicerContext) \
            -> AxisSystemPositionController_pb2.Subscribe_Position_Responses:
        """
        Requests the observable property Position
            The current XY position of the axis system

        :param request: An empty gRPC request object (properties have no parameters)
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A response object with the following fields:
            Position (Position): The current XY position of the axis system
        """

        new_position = self.axis_system.get_actual_position_xy()
        position = new_position.x + 1  # force sending the first value
        while not self.system.state.shutting_down():
            new_position = self.axis_system.get_actual_position_xy()
            if not math.isclose(new_position.x, position.x) or \
                not math.isclose(new_position.y, position.y):
                position = new_position
                yield AxisSystemPositionController_pb2.Subscribe_Position_Responses(
                    Position=AxisSystemPositionController_pb2.
                    DataType_Position(
                        Position=AxisSystemPositionController_pb2.
                        DataType_Position.Position_Struct(
                            X=silaFW_pb2.Real(value=position.x),
                            Y=silaFW_pb2.Real(value=position.y))))
            time.sleep(0.1)  # give client some time to catch up
Beispiel #6
0
    def MoveToPosition(self, request, context: grpc.ServicerContext) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Executes the observable command "Move To Position"
            Move the axis system to the given position with a certain velocity

        :param request: gRPC request containing the parameters passed:
            request.Position (Position): The position to move to
            request.Velocity (Velocity): A real value between 0 (exclusive) and 100 (inclusive) defining the relative speed at which all axes of the axis system should move.The velocity value is multiplied with the maximum velocity value of each axis. So a value of 100 means, all axes travel with their maximum velocity. A value of 50 means, all axes travel with the half of the maximum velocity.
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A command confirmation object with the following information:
            commandId: A command id with which this observable command can be referenced in future calls
            lifetimeOfExecution: The (maximum) lifetime of this command call.
        """

        # initialise default values
        #: Duration silaFW_pb2.Duration(seconds=<seconds>, nanos=<nanos>)
        lifetime_of_execution: silaFW_pb2.Duration = None

        # TODO:
        #   Execute the actual command
        #   Optional: Generate a lifetime_of_execution

        # respond with UUID and lifetime of execution
        command_uuid = silaFW_pb2.CommandExecutionUUID(value=str(uuid.uuid4()))
        if lifetime_of_execution is not None:
            return silaFW_pb2.CommandConfirmation(
                commandExecutionUUID=command_uuid,
                lifetimeOfExecution=lifetime_of_execution)
        else:
            return silaFW_pb2.CommandConfirmation(
                commandExecutionUUID=command_uuid)
    def Shutdown(self, request, context: grpc.ServicerContext) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Executes the observable command "Shutdown"
            Initiates the shutdown routine. If no errors occurred during the shutdown process the server should be considered ready to be physically shutdown (i.e. the device can be shut down/powered off).

        :param request: gRPC request containing the parameters passed:
            request.EmptyParameter (Empty Parameter): An empty parameter data type used if no parameter is required.
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A command confirmation object with the following information:
            commandId: A command id with which this observable command can be referenced in future calls
            lifetimeOfExecution: The (maximum) lifetime of this command call.
        """

        # initialise default values
        #: Duration silaFW_pb2.Duration(seconds=<seconds>, nanos=<nanos>)
        lifetime_of_execution: silaFW_pb2.Duration = None

        # TODO:
        #   Execute the actual command
        #   Optional: Generate a lifetime_of_execution

        # respond with UUID and lifetime of execution
        command_uuid = silaFW_pb2.CommandExecutionUUID(value=str(uuid.uuid4()))
        if lifetime_of_execution is not None:
            return silaFW_pb2.CommandConfirmation(
                commandExecutionUUID=command_uuid,
                lifetimeOfExecution=lifetime_of_execution
            )
        else:
            return silaFW_pb2.CommandConfirmation(
                commandExecutionUUID=command_uuid
            )
    def _get_initialization_state(self) -> silaFW_pb2.ExecutionInfo:
        """
        Method to fill an ExecutionInfo message from the SiLA server for the InitializeContiflow observable command

        :return: An execution info object with the current command state
        """

        #: Enumeration of silaFW_pb2.ExecutionInfo.CommandStatus
        command_status = silaFW_pb2.ExecutionInfo.CommandStatus.waiting
        if self.pump.is_initializing() and not self.timer.is_expired():
            command_status = silaFW_pb2.ExecutionInfo.CommandStatus.running
        elif self.pump.is_initialized():
            command_status = silaFW_pb2.ExecutionInfo.CommandStatus.finishedSuccessfully
        else:
            command_status = silaFW_pb2.ExecutionInfo.CommandStatus.finishedWithError
        #: Duration silaFW_pb2.Duration(seconds=<seconds>, nanos=<nanos>)
        command_estimated_remaining = self.timer.get_msecs_to_expiration(
        ) / 1000

        return silaFW_pb2.ExecutionInfo(
            commandStatus=command_status,
            estimatedRemainingTime=silaFW_pb2.Duration(
                seconds=int(command_estimated_remaining)),
            updatedLifetimeOfExecution=silaFW_pb2.Duration(
                seconds=int(self.timer.period_ms / 1000)))
    def InitializePumpDrive(self, request, context: grpc.ServicerContext) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Executes the observable command "Initialize Pump Drive"
            Initialize the pump drive (e.g. by executing a reference move).

        :param request: gRPC request containing the parameters passed:
            request.EmptyParameter (Empty Parameter): An empty parameter data type used if no parameter is required.
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A command confirmation object with the following information:
            commandId: A command id with which this observable command can be referenced in future calls
            lifetimeOfExecution: The (maximum) lifetime of this command call.
        """

        # initialise default values
        #: Duration silaFW_pb2.Duration(seconds=<seconds>, nanos=<nanos>)
        lifetime_of_execution: silaFW_pb2.Duration = None

        # TODO:
        #   Execute the actual command
        #   Optional: Generate a lifetime_of_execution

        # respond with UUID and lifetime of execution
        command_uuid = silaFW_pb2.CommandExecutionUUID(value=str(uuid.uuid4()))
        if lifetime_of_execution is not None:
            return silaFW_pb2.CommandConfirmation(
                commandExecutionUUID=command_uuid,
                lifetimeOfExecution=lifetime_of_execution
            )
        else:
            return silaFW_pb2.CommandConfirmation(
                commandExecutionUUID=command_uuid
            )
Beispiel #10
0
    def InitializePumpDrive(self, request, context: grpc.ServicerContext) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Executes the observable command "Initialize Pump Drive"
            Initialize the pump drive (e.g. by executing a reference move).

        :param request: gRPC request containing the parameters passed:
            request.EmptyParameter (Empty Parameter): An empty parameter data type used if no parameter is required.
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A command confirmation object with the following information:
            commandId: A command id with which this observable command can be referenced in future calls
            lifetimeOfExecution: The (maximum) lifetime of this command call.
        """

        if not self.system.state.is_operational():
            raise SystemNotOperationalError(
                'de.cetoni/pumps.syringepumps/PumpDriveControlService/v1/Command/InitializePumpDrive'
            )

        if self.calibration_uuid:
            raise InitializationNotFinishedError()

        self.pump.calibrate()

        self.calibration_uuid = str(uuid.uuid4())
        return silaFW_pb2.CommandConfirmation(
            commandExecutionUUID=silaFW_pb2.CommandExecutionUUID(
                value=self.calibration_uuid),
            lifetimeOfExecution=silaFW_pb2.Duration(
                seconds=self.CALIBRATION_TIMEOUT + 1))
Beispiel #11
0
    def SetSyringeParameters(self, InnerDiameter: float, MaxPistonStroke: float): # -> (SyringeConfigurationController):
        """
        Wrapper to call the unobservable command SetSyringeParameters on the server.

        :param InnerDiameter: Inner diameter of the syringe tube in millimetres
        :param MaxPistonStroke: The maximum piston stroke defines the maximum position
                                the piston can be moved to before it slips out of
                                the syringe tube. The maximum piston stroke limits
                                the maximum travel range of the syringe pump pusher.

        :returns: A gRPC object with the response that has been defined for this command.
        """
        # noinspection PyUnusedLocal - type definition, just for convenience
        grpc_err: grpc.Call

        logging.debug("Calling SetSyringeParameters:")
        try:
            parameter = SyringeConfigurationController_pb2.SetSyringeParameters_Parameters(
                InnerDiameter=silaFW_pb2.Real(value=InnerDiameter),
                MaxPistonStroke=silaFW_pb2.Real(value=MaxPistonStroke)
            )

            response = self.SyringeConfigurationController_stub.SetSyringeParameters(parameter)
            logging.debug(f"SetSyringeParameters response: {response}")

        except grpc.RpcError as grpc_err:
            self.grpc_error_handling(grpc_err)
            return None

        return
Beispiel #12
0
    def MoveToPosition(self, request, context: grpc.ServicerContext) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Executes the observable command "Move To Position"
            Move the axis to the given position with a certain velocity

        :param request: gRPC request containing the parameters passed:
            request.Position (Position): The position to move to. Has to be in the range between MinimumPosition and MaximumPosition. See PositionUnit for the unit that is used for a specific axis. E.g. for rotational axis systems one of the axes needs a position specified in radians.
            request.Velocity (Velocity): The velocity value for the movement. Has to be in the range between MinimumVelocity and MaximumVelocity.
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A command confirmation object with the following information:
            commandId: A command id with which this observable command can be referenced in future calls
            lifetimeOfExecution: The (maximum) lifetime of this command call.
        """

        requested_position = request.Position.value
        requested_velocity = request.Velocity.Velocity.value

        axis = self._get_axis(context.invocation_metadata())
        self._validate_position(axis, requested_position)
        self._validate_velocity(axis, requested_velocity)

        command_uuid = str(uuid.uuid4())
        self.uuid_to_axis[command_uuid] = axis

        axis.move_to_position(requested_position, requested_velocity)
        logging.info(
            f"Started moving to {requested_position} with velocity of {requested_velocity}"
        )

        return silaFW_pb2.CommandConfirmation(
            commandExecutionUUID=silaFW_pb2.CommandExecutionUUID(
                value=command_uuid))
Beispiel #13
0
    def SetFillLevel(self, FillLevel: float, FlowRate: float) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Wrapper to call the observable command SetFillLevel on the server.

        :param FillLevel: The requested fill level. A level of 0 indicates a completely
                          empty syringe. The value has to be between 0 and MaxSyringeFillLevel.
                          Depending on the requested fill level this may cause aspiration
                          or dispension of fluid.
        :param FlowRate: The flow rate at which the pump should dose the fluid.

        :returns: A command confirmation object with the following information:
            commandExecutionUUID: A command id with which this observable command can be referenced in future calls
            lifetimeOfExecution (optional): The (maximum) lifetime of this command call.
        """
        # noinspection PyUnusedLocal - type definition, just for convenience
        grpc_err: grpc.Call

        logging.debug("Calling SetFillLevel:")
        try:
            parameter = PumpFluidDosingService_pb2.SetFillLevel_Parameters(
                FillLevel=silaFW_pb2.Real(value=FillLevel),
                FlowRate=silaFW_pb2.Real(value=FlowRate))

            response = self.PumpFluidDosingService_stub.SetFillLevel(parameter)

            logging.debug(
                'SetFillLevel response: {response}'.format(response=response))
        except grpc.RpcError as grpc_err:
            self.grpc_error_handling(grpc_err)
            return None

        return response
Beispiel #14
0
    def DoseVolume(self, Volume: float, FlowRate: float) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Wrapper to call the observable command DoseVolume on the server.

        :param Volume: The amount of volume to dose. This value can be negative.
                       In that case the pump aspirates the fluid.
        :param FlowRate: The flow rate at which the pump should dose the fluid.

        :returns: A command confirmation object with the following information:
            commandExecutionUUID: A command id with which this observable command can be referenced in future calls
            lifetimeOfExecution (optional): The (maximum) lifetime of this command call.
        """
        # noinspection PyUnusedLocal - type definition, just for convenience
        grpc_err: grpc.Call

        logging.debug("Calling DoseVolume:")
        try:
            parameter = PumpFluidDosingService_pb2.DoseVolume_Parameters(
                Volume=silaFW_pb2.Real(value=Volume),
                FlowRate=silaFW_pb2.Real(value=FlowRate))

            response = self.PumpFluidDosingService_stub.DoseVolume(parameter)

            logging.debug(
                'DoseVolume response: {response}'.format(response=response))
        except grpc.RpcError as grpc_err:
            self.grpc_error_handling(grpc_err)
            return None

        return response
Beispiel #15
0
    def Get_FCPAffectedByMetadata_ChannelIndex(self, request, context: grpc.ServicerContext) \
            -> AnalogOutChannelController_pb2.Get_FCPAffectedByMetadata_ChannelIndex_Responses:
        """
        Requests the unobservable property FCPAffectedByMetadata Channel Index
            Specifies which Features/Commands/Properties of the SiLA server are affected by the Channel Index Metadata.

        :param request: An empty gRPC request object (properties have no parameters)
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A response object with the following fields:
            AffectedCalls (AffectedCalls): A string containing a list of Fully Qualified Identifiers of Features, Commands and Properties for which the SiLA Client Metadata is expected as part of the respective RPCs.
        """

        logging.debug(
            "Property FCPAffectedByMetadata_ChannelIndex requested in {current_mode} mode"
            .format(current_mode=(
                'simulation' if self.simulation_mode else 'real')))
        return AnalogOutChannelController_pb2.Get_FCPAffectedByMetadata_ChannelIndex_Responses(
            AffectedCalls=[
                silaFW_pb2.String(
                    value=
                    "de.cetoni/io/AnalogOutChannelController/v1/Command/SetOutputValue"
                ),
                silaFW_pb2.String(
                    value=
                    "de.cetoni/io/AnalogOutChannelController/v1/Property/Value"
                ),
            ])
Beispiel #16
0
    def SetFlowUnit(self, VolumeUnit: str,
                    TimeUnit: str):  # -> (PumpUnitController):
        """
        Wrapper to call the unobservable command SetFlowUnit on the server.

        :param VolumeUnit: The volume unit of the flow rate (e.g. 'l' for 'litres')
        :param TimeUnit: The time unit of the flow rate (e.g. 'h' for 'hours' or 's' for 'seconds')

        :returns: A gRPC object with the response that has been defined for this command.
        """
        # noinspection PyUnusedLocal - type definition, just for convenience
        grpc_err: grpc.Call

        logging.debug("Calling SetFlowUnit:")
        try:
            parameter = PumpUnitController_pb2.SetFlowUnit_Parameters(
                FlowUnit=PumpUnitController_pb2.SetFlowUnit_Parameters.
                SetFlowUnit_Struct(
                    VolumeUnit=PumpUnitController_pb2.DataType_VolumeUnit(
                        VolumeUnit=silaFW_pb2.String(value=VolumeUnit)),
                    TimeUnit=PumpUnitController_pb2.DataType_TimeUnit(
                        TimeUnit=silaFW_pb2.String(value=TimeUnit))))

            response = self.PumpUnitController_stub.SetFlowUnit(parameter)
            logging.debug(f"SetFlowUnit response: {response}")

        except grpc.RpcError as grpc_err:
            self.grpc_error_handling(grpc_err)
            return None

        return
    def RunControlLoop(self, request, controller, context: grpc.ServicerContext) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Executes the observable command "Run Control Loop"
            Run the Control Loop

        :param request: gRPC request containing the parameters passed
        :param controller: The controller to operate on
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A command confirmation object with the following information:
            commandId: A command id with which this observable command can be referenced in future calls
            lifetimeOfExecution: The (maximum) lifetime of this command call.
        """

        # initialise default values
        #: Duration silaFW_pb2.Duration(seconds=<seconds>, nanos=<nanos>)
        lifetime_of_execution: silaFW_pb2.Duration = None

        # TODO:
        #   Execute the actual command
        #   Optional: Generate a lifetime_of_execution

        # respond with UUID and lifetime of execution
        command_uuid = silaFW_pb2.CommandExecutionUUID(value=str(uuid.uuid4()))
        if lifetime_of_execution is not None:
            return silaFW_pb2.CommandConfirmation(
                commandExecutionUUID=command_uuid,
                lifetimeOfExecution=lifetime_of_execution)
        else:
            return silaFW_pb2.CommandConfirmation(
                commandExecutionUUID=command_uuid)
    def GenerateFlow(self, request, context: grpc.ServicerContext) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Executes the observable command "Generate Flow"
            Generate a continuous flow with the given flow rate. Dosing continues until it gets stopped manually by calling StopDosage or until the pusher reached one of its limits.

        :param request: gRPC request containing the parameters passed:
            request.FlowRate (Flow Rate):
            The flow rate at which the pump should dose the fluid. This value can be negative. In that case the pump aspirates the fluid.
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A command confirmation object with the following information:
            commandId: A command id with which this observable command can be referenced in future calls
            lifetimeOfExecution: The (maximum) lifetime of this command call.
        """

        # initialise default values
        #: Duration silaFW_pb2.Duration(seconds=<seconds>, nanos=<nanos>)
        lifetime_of_execution: silaFW_pb2.Duration = None

        # TODO:
        #   Execute the actual command
        #   Optional: Generate a lifetime_of_execution

        # respond with UUID and lifetime of execution
        command_uuid = silaFW_pb2.CommandExecutionUUID(value=str(uuid.uuid4()))
        if lifetime_of_execution is not None:
            return silaFW_pb2.CommandConfirmation(
                commandExecutionUUID=command_uuid,
                lifetimeOfExecution=lifetime_of_execution)
        else:
            return silaFW_pb2.CommandConfirmation(
                commandExecutionUUID=command_uuid)
Beispiel #19
0
    def InitializeContiflow(self, request, context: grpc.ServicerContext) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Executes the observable command "Initialize Contiflow"
            Initialize the continuous flow pump.
            Call this command after all parameters have been set, to prepare the conti flow pump for the start of the continuous flow. The initialization procedure ensures, that the syringes are sufficiently filled to start the continuous flow. So calling this command may cause a syringe refill if the syringes are not sufficiently filled. So before calling this command you should ensure, that syringe refilling properly works an can be executed. If you have a certain syringe refill procedure, you can also manually refill the syringes with the normal syringe pump functions. If the syringes are sufficiently filled if you call this function, no refilling will take place.

        :param request: gRPC request containing the parameters passed:
            request.EmptyParameter (Empty Parameter): An empty parameter data type used if no parameter is required.
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A command confirmation object with the following information:
            commandId: A command id with which this observable command can be referenced in future calls
            lifetimeOfExecution: The (maximum) lifetime of this command call.
        """

        # initialise default values
        #: Duration silaFW_pb2.Duration(seconds=<seconds>, nanos=<nanos>)
        lifetime_of_execution: silaFW_pb2.Duration = None

        # TODO:
        #   Execute the actual command
        #   Optional: Generate a lifetime_of_execution

        # respond with UUID and lifetime of execution
        command_uuid = silaFW_pb2.CommandExecutionUUID(value=str(uuid.uuid4()))
        if lifetime_of_execution is not None:
            return silaFW_pb2.CommandConfirmation(
                commandExecutionUUID=command_uuid,
                lifetimeOfExecution=lifetime_of_execution)
        else:
            return silaFW_pb2.CommandConfirmation(
                commandExecutionUUID=command_uuid)
    def DoseVolume(self, request, context: grpc.ServicerContext) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Executes the observable command "Dose Volume"
            Dose a certain amount of volume with the given flow rate.

        :param request: gRPC request containing the parameters passed:
            request.Volume (Volume):
            The amount of volume to dose. This value can be negative. In that case the pump aspirates the fluid.
            request.FlowRate (Flow Rate): The flow rate at which the pump should dose the fluid.
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A command confirmation object with the following information:
            commandId: A command id with which this observable command can be referenced in future calls
            lifetimeOfExecution: The (maximum) lifetime of this command call.
        """

        if not self.system.state.is_operational():
            raise SystemNotOperationalError('de.cetoni/pumps.syringepumps/v1/Command/DoseVolume')

        requested_volume = request.Volume.value
        requested_flow_rate = request.FlowRate.value

        # requested_volume is negative to indicate aspiration of fluid.
        # Since the pre dosage checks test against 0 and the max flow rate of
        # the pump, we pass the absolute value of the requested_flow_rate.
        self._check_pre_dosage(command='DoseVolume', flow_rate=requested_flow_rate,
                               volume=abs(requested_volume))

        # Give clearer error messages:
        # QmixSDK would just start and immediately stop dosing in case of dispense
        # from empty syringe or aspirate to full syringe.
        pump_fill_level = self.pump.get_fill_level()
        if requested_volume > pump_fill_level:
            raise VolumeOutOfRangeError(
                command='DoseVolume',
                msg="There is too less fluid in the syringe! Aspirate some fluid before dispensing!"
            )
        # negative to indicate that there is still space for mor fluid
        free_volume = pump_fill_level - self.pump.get_volume_max()
        if requested_volume < free_volume:
            raise VolumeOutOfRangeError(
                command='DoseVolume',
                msg="There is too much fluid in the syringe! Dispense some fluid before aspirating!"
            )

        self.dosage_uuid = str(uuid.uuid4())
        command_uuid = silaFW_pb2.CommandExecutionUUID(value=self.dosage_uuid)

        self.pump.pump_volume(requested_volume, requested_flow_rate)
        logging.info("Started dosing a volume of %s with a flow rate of %5.2f (UUID: %s)",
                     requested_volume, requested_flow_rate, self.dosage_uuid)
        return silaFW_pb2.CommandConfirmation(commandExecutionUUID=command_uuid)
    def GenerateFlow(self, request, context: grpc.ServicerContext) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Executes the observable command "Generate Flow"
            Generate a continuous flow with the given flow rate. Dosing continues until it gets stopped manually by calling StopDosage or until the pusher reached one of its limits.

        :param request: gRPC request containing the parameters passed:
            request.FlowRate (Flow Rate):
            The flow rate at which the pump should dose the fluid. This value can be negative. In that case the pump aspirates the fluid.
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A command confirmation object with the following information:
            commandId: A command id with which this observable command can be referenced in future calls
            lifetimeOfExecution: The (maximum) lifetime of this command call.
        """

        if not self.system.state.is_operational():
            raise SystemNotOperationalError('de.cetoni/pumps.syringepumps/v1/Command/GenerateFlow')

        requested_flow_rate = request.FlowRate.value

        # requested_flow_rate is negative to indicate aspiration of fluid.
        # Since the pre dosage checks test against 0 and the max flow rate of
        # the pump, we pass the absolute value of the requested_flow_rate.
        self._check_pre_dosage(command='GenerateFlow', flow_rate=abs(requested_flow_rate))

        # NOTE:
        # _check_pre_dosage throws an error if the flow rate is <= 0
        # but we should allow a flow rate == 0 -> this results in stopping a dosage

        # Give clearer error messages:
        # QmixSDK would just start and immediately stop dosing in case of dispense
        # from empty syringe or aspirate to full syringe.
        if requested_flow_rate > 0 and self.pump.get_fill_level() == 0:
            raise FlowRateOutOfRangeError(
                command='GenerateFlow',
                msg="Cannot dispense from an empty syringe! Aspirate some fluid before dispensing!"
            )
        if requested_flow_rate < 0 and self.pump.get_fill_level() == self.pump.get_volume_max():
            raise FlowRateOutOfRangeError(
                command='GenerateFlow',
                msg="Cannot aspirate to a full syringe! Dispense some fluid before aspirating!"
            )

        self.dosage_uuid = str(uuid.uuid4())
        command_uuid = silaFW_pb2.CommandExecutionUUID(value=self.dosage_uuid)

        self.pump.generate_flow(requested_flow_rate)
        logging.info("Started dosing with a flow rate of %5.2f (UUID: %s)",
                     requested_flow_rate, self.dosage_uuid)
        return silaFW_pb2.CommandConfirmation(commandExecutionUUID=command_uuid)
Beispiel #22
0
    def Get_CameraPicture(self, request, context: grpc.ServicerContext) \
            -> Ot2Controller_pb2.Get_CameraPicture_Responses:
        """
        Requests the unobservable property Camera Picture
            A current picture from the inside of the OT-2 made with the built-in camera.
            See https://support.opentrons.com/en/articles/2831465-using-the-ot-2-s-camera

        :param request: An empty gRPC request object (properties have no parameters)
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A response object with the following fields:
            request.CameraPicture (Camera Picture): A current picture from the inside of the OT-2 made with the built-in
            camera.
        """
        out_image_file: str = "/tmp/tmp_image.jpeg"
        cmd: str = f"ffmpeg -y -f video4linux2 -s 640x480 -i /dev/video0 -ss 0:0:1 -frames 1 {out_image_file}"
        logging.debug(f"run '{cmd}'")
        ssh_stdin, ssh_stdout, ssh_stderr = self.ssh.exec_command(cmd)
        run_ret: int = ssh_stdout.channel.recv_exit_status()
        logging.debug("run returned '" + str(run_ret) + "'")

        scp = SCPClient(self.ssh.get_transport())
        try:
            scp.get(out_image_file, "/tmp/tmp_image.jpeg", recursive=False)
        except SCPException as error:
            logging.error(error)
            raise
        finally:
            scp.close()

        logging.debug(f"Downloaded {out_image_file} to /tmp/tmp_image.jpeg")
        img_bytes = open("/tmp/tmp_image.jpeg", 'rb').read()

        ts: datetime = datetime.datetime.now(datetime.timezone.utc)
        timezone = silaFW_pb2.Timezone(hours=0, minutes=0)
        timestamp = silaFW_pb2.Timestamp(year=ts.year,
                                         month=ts.month,
                                         day=ts.day,
                                         hour=ts.hour,
                                         minute=ts.minute,
                                         second=ts.second,
                                         timezone=timezone)

        cam_pic_struct = Ot2Controller_pb2.Get_CameraPicture_Responses.CameraPicture_Struct(
            ImageData=silaFW_pb2.Binary(value=img_bytes),
            ImageTimestamp=timestamp)

        return Ot2Controller_pb2.Get_CameraPicture_Responses(CameraPicture=cam_pic_struct)
    def Subscribe_Value(self, request, channel, context: grpc.ServicerContext) \
            -> AnalogInChannelProvider_pb2.Subscribe_Value_Responses:
        """
        Requests the observable property Value
            The value of the analog input channel.

        :param request: An empty gRPC request object (properties have no parameters)
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A response object with the following fields:
            Value (Value): The value of the analog input channel.
        """

        # initialise the return value
        return_value: AnalogInChannelProvider_pb2.Subscribe_Value_Responses = None

        # we could use a timeout here if we wanted
        while True:
            # TODO:
            #   Add implementation of Simulation for property Value here and write the resulting
            #   response in return_value

            # create the default value
            if return_value is None:
                return_value = AnalogInChannelProvider_pb2.Subscribe_Value_Responses(
                    #**default_dict['Subscribe_Value_Responses']
                    Value=silaFW_pb2.Real(value=1.0))

            yield return_value
    def Shutdown_Result(self, uuid: Union[str, silaFW_pb2.CommandExecutionUUID]) \
            -> ShutdownController_pb2.Shutdown_Responses:
        """
        Wrapper to get an intermediate response for the observable command Shutdown on the server.

        :param uuid: The UUID that has been returned with the first command call. Can be given as string or as the
                     corresponding SiLA2 gRPC object.

        :returns: A gRPC object with the result response that has been defined for this command.

        .. note:: Whether the result is available or not can and should be evaluated by calling the
                  :meth:`Shutdown_Info` method of this call.
        """
        if type(uuid) is str:
            uuid = silaFW_pb2.CommandExecutionUUID(value=uuid)

        logging.debug(
            "Requesting status information for command Shutdown (UUID={uuid}):".format(
                uuid=uuid.value
            )
        )

        try:
            response = self.ShutdownController_stub.Shutdown_Result(uuid)
            logging.debug('Shutdown result response: {response}'.format(response=response))
        except grpc.RpcError as grpc_err:
            self.grpc_error_handling(grpc_err)
            return None

        return response
    def Shutdown_Info(self, uuid: Union[str, silaFW_pb2.CommandExecutionUUID]) \
            -> silaFW_pb2.ExecutionInfo:
        """
        Wrapper to get an intermediate response for the observable command Shutdown on the server.

        :param uuid: The UUID that has been returned with the first command call. Can be given as string or as the
                     corresponding SiLA2 gRPC object.

        :returns: A gRPC object with the status information that has been defined for this command. The following fields
                  are defined:
                    * *commandStatus*: Status of the command (enumeration)
                    * *progressInfo*: Information on the progress of the command (0 to 1)
                    * *estimatedRemainingTime*: Estimate of the remaining time required to run the command
                    * *updatedLifetimeOfExecution*: An update on the execution lifetime
        """
        # noinspection PyUnusedLocal - type definition, just for convenience
        grpc_err: grpc.Call

        if type(uuid) is str:
            uuid = silaFW_pb2.CommandExecutionUUID(value=uuid)

        logging.debug(
            "Requesting status information for command Shutdown (UUID={uuid}):".format(
                uuid=uuid.value
            )
        )
        try:
            response = self.ShutdownController_stub.Shutdown_Info(uuid)
            logging.debug('Shutdown status information: {response}'.format(response=response))
        except grpc.RpcError as grpc_err:
            self.grpc_error_handling(grpc_err)
            return None

        return response
Beispiel #26
0
    def _get_command_state(self, command_uuid: str) -> silaFW_pb2.ExecutionInfo:
        """
        Method to fill an ExecutionInfo message from the SiLA server for observable commands

        :param command_uuid: The uuid of the command for which to return the current state

        :return: An execution info object with the current command state
        """

        #: Enumeration of silaFW_pb2.ExecutionInfo.CommandStatus
        command_status = silaFW_pb2.ExecutionInfo.CommandStatus.waiting
        #: Real silaFW_pb2.Real(0...1)
        command_progress = None
        #: Duration silaFW_pb2.Duration(seconds=<seconds>, nanos=<nanos>)
        command_estimated_remaining = None
        #: Duration silaFW_pb2.Duration(seconds=<seconds>, nanos=<nanos>)
        command_lifetime_of_execution = None

        # TODO: check the state of the command with the given uuid and return the correct information

        # just return a default in this example
        return silaFW_pb2.ExecutionInfo(
            commandStatus=command_status,
            progressInfo=(command_progress if command_progress is not None else None),
            estimatedRemainingTime=(command_estimated_remaining if command_estimated_remaining is not None else None),
            updatedLifetimeOfExecution=(
                command_lifetime_of_execution if command_lifetime_of_execution is not None else None)
        )
    def MoveToPosition_Result(self, axis_id: str,
                              uuid: Union[str, silaFW_pb2.CommandExecutionUUID]) \
            -> AxisPositionController_pb2.MoveToPosition_Responses:
        """
        Wrapper to get an intermediate response for the observable command MoveToPosition on the server.

        :param axis_id: The index of the axis to use. Will be sent along as metadata
                        of the call
        :param uuid: The UUID that has been returned with the first command call. Can be given as string or as the
                     corresponding SiLA2 gRPC object.

        :returns: A gRPC object with the result response that has been defined for this command.

        .. note:: Whether the result is available or not can and should be evaluated by calling the
                  :meth:`MoveToPosition_Info` method of this call.
        """
        if type(uuid) is str:
            uuid = silaFW_pb2.CommandExecutionUUID(value=uuid)

        logging.debug(
            "Requesting status information for command MoveToPosition for axis {axis_id} (UUID={uuid}):"
            .format(axis_id=axis_id, uuid=uuid.value))

        try:
            metadata = ((METADATA_AXIS_IDENTIFIER,
                         self._serialize_axis_id(axis_id)), )
            response = self.AxisPositionController_stub.MoveToPosition_Result(
                uuid, metadata=metadata)
            logging.debug('MoveToPosition result response: {response}'.format(
                response=response))
        except grpc.RpcError as grpc_err:
            self.grpc_error_handling(grpc_err)
            return None

        return response
Beispiel #28
0
    def Subscribe_IsInitialized(self, request, context: grpc.ServicerContext) \
            -> ContinuousFlowInitializationController_pb2.Subscribe_IsInitialized_Responses:
        """
        Requests the observable property Is Initialized
            Returns true, if the continuous fow pump is initialized and ready for continuous flow start.
            Use this function to check if the pump is initialized before you start a continuous flow. If you change and continuous flow parameter, like valve settings, cross flow duration and so on, the pump will leave the initialized state. That means, after each parameter change, an initialization is required. Changing the flow rate or the dosing volume does not require and initialization.


        :param request: An empty gRPC request object (properties have no parameters)
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A response object with the following fields:
            IsInitialized (Is Initialized): Returns true, if the continuous fow pump is initialized and ready for continuous flow start.
            Use this function to check if the pump is initialized before you start a continuous flow. If you change and continuous flow parameter, like valve settings, cross flow duration and so on, the pump will leave the initialized state. That means, after each parameter change, an initialization is required. Changing the flow rate or the dosing volume does not require and initialization.
        """

        # initialise the return value
        return_value: ContinuousFlowInitializationController_pb2.Subscribe_IsInitialized_Responses = None

        # we could use a timeout here if we wanted
        while True:
            # TODO:
            #   Add implementation of Simulation for property IsInitialized here and write the resulting
            #   response in return_value

            # create the default value
            if return_value is None:
                return_value = ContinuousFlowInitializationController_pb2.Subscribe_IsInitialized_Responses(
                    #**default_dict['Subscribe_IsInitialized_Responses']
                    IsInitialized=silaFW_pb2.Boolean(value=False))

            yield return_value
Beispiel #29
0
    def Subscribe_MaxFlowRate(self, request, context: grpc.ServicerContext) \
            -> ContinuousFlowDosingService_pb2.Subscribe_MaxFlowRate_Responses:
        """
        Requests the observable property Maximum Flow Rate
            The maximum value of the flow rate at which this pump can dose a fluid.

        :param request: An empty gRPC request object (properties have no parameters)
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A response object with the following fields:
            MaxFlowRate (Maximum Flow Rate): The maximum value of the flow rate at which this pump can dose a fluid.
        """

        new_max_flow_rate = self.pump.get_flow_rate_max()
        max_flow_rate = new_max_flow_rate + 1 # force sending the first value
        while not self.system.state.shutting_down():
            if self.system.state.is_operational():
                new_max_flow_rate = self.pump.get_flow_rate_max()
            if not math.isclose(new_max_flow_rate, max_flow_rate):
                max_flow_rate = new_max_flow_rate
                yield ContinuousFlowDosingService_pb2.Subscribe_MaxFlowRate_Responses(
                    MaxFlowRate=silaFW_pb2.Real(value=max_flow_rate)
                )
            # we add a small delay to give the client a chance to keep up.
            time.sleep(0.1)
    def Subscribe_FlowRate(self, request, context: grpc.ServicerContext) \
            -> PumpFluidDosingService_pb2.Subscribe_FlowRate_Responses:
        """
        Requests the observable property Flow Rate
            The current value of the flow rate. It is 0 if the pump does not dose any fluid.

        :param request: An empty gRPC request object (properties have no parameters)
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: A response object with the following fields:
            FlowRate (Flow Rate): The current value of the flow rate. It is 0 if the pump does not dose any fluid.
        """

        # initialise the return value
        return_value: PumpFluidDosingService_pb2.Subscribe_FlowRate_Responses = None

        # we could use a timeout here if we wanted
        while True:
            # TODO:
            #   Add implementation of Simulation for property FlowRate here and write the resulting
            #   response in return_value

            # create the default value
            if return_value is None:
                return_value = PumpFluidDosingService_pb2.Subscribe_FlowRate_Responses(
                    #**default_dict['Subscribe_FlowRate_Responses']
                    FlowRate=silaFW_pb2.Real(value=1.0))

            yield return_value