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
            )
예제 #2
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 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)
예제 #4
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 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)
    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)
    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 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))
예제 #9
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))
예제 #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))
    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)
    def SetFillLevel(self, request, context: grpc.ServicerContext) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Executes the observable command "Set Fill Level"
            Pumps fluid with the given flow rate until the requested fill level is reached.
            Depending on the requested fill level given in the FillLevel parameter this function may cause aspiration or dispension of fluid.

        :param request: gRPC request containing the parameters passed:
            request.FillLevel (Fill Level):
            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.
            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.
        """

        # 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 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.
        """

        requested_position = geom.Point(request.Position.Position.X.value,
                                        request.Position.Position.Y.value)
        requested_velocity = request.Velocity.value

        self._ensure_stopped()
        self._validate_position(requested_position)

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

        try:
            self.axis_system.move_to_postion_xy(requested_position.x,
                                                requested_position.y,
                                                requested_velocity / 100)
        except DeviceError as err:
            if err.errorcode == -1:  # Operation not permitted
                raise MovementBlockedError()
            else:
                raise err

        logging.info(
            f"Started moving to {requested_position} with {requested_velocity}% of max velocity"
        )

        # respond with UUID and lifetime of execution
        return silaFW_pb2.CommandConfirmation(
            commandExecutionUUID=command_uuid)
예제 #15
0
    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.

        :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 cannot be negative since dosing is meant to only work in one direction.
        :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_flow_rate = request.FlowRate.value

        # We only allow one dosage at a time.
        # -> Stop the currently running dosage and after that start the new one.
        if self.dosage_uuid:
            self.StopDosage(0, 0)
            # wait for the currently running dosage to catch up
            time.sleep(0.25)

        msg = "The requested flow rate ({requested_val} {unit}) has to be in the range \
            between 0 {unit} and {max_val} {unit} for this pump."
        max_flow_rate = self.pump.get_flow_rate_max()
        if requested_flow_rate < 0 or requested_flow_rate > max_flow_rate:
            unit = uc.flow_unit_to_string(self.pump.get_flow_unit())
            raise SiLAValidationError(
                parameter=f"de.cetoni/pumps.contiflowpumps/ContinuousFlowDosingService/v1/Command/GenerateFlow/FlowRate",
                msg=msg.format(requested_val=requested_flow_rate, unit=unit, max_val=max_flow_rate)
            )

        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)
    def RunControlLoop(self, request, controller: ControllerChannel, 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.
        """

        controller.enable_control_loop(True)
        result = ('' if controller.is_control_loop_enabled() else 'not ') + 'successful'
        logging.debug(f"Starting control loop with set point {controller.get_setpoint()} was {result}")

        # respond with UUID and lifetime of execution
        return silaFW_pb2.CommandConfirmation(
            commandExecutionUUID=silaFW_pb2.CommandExecutionUUID(value=str(uuid.uuid4()))
        )
    def SetFillLevel(self, request, context: grpc.ServicerContext) \
            -> silaFW_pb2.CommandConfirmation:
        """
        Executes the observable command "Set Fill Level"
            Pumps fluid with the given flow rate until the requested fill level is reached.
            Depending on the requested fill level given in the FillLevel parameter this function may cause aspiration or dispension of fluid.

        :param request: gRPC request containing the parameters passed:
            request.FillLevel (Fill Level):
            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.
            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/SetFillLevel')

        requested_fill_level = request.FillLevel.value
        requested_flow_rate = request.FlowRate.value

        self._check_pre_dosage(command='SetFillLevel', flow_rate=requested_flow_rate,
                               fill_level=requested_fill_level)

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

        self.pump.set_fill_level(requested_fill_level, requested_flow_rate)
        logging.info("Started dosing with flow rate of %5.2f until fill level of %5.2f is reached (UUID: %s)",
                     requested_flow_rate, requested_fill_level, self.dosage_uuid)

        # respond with UUID and lifetime of execution
        return silaFW_pb2.CommandConfirmation(commandExecutionUUID=command_uuid)