Example #1
0
    def WriteSetPoint(self, request, context: grpc.ServicerContext) \
            -> ControlLoopService_pb2.WriteSetPoint_Responses:
        """
        Executes the unobservable command "Write Set Point"
            Write a Set Point value to the Controller Device

        :param request: gRPC request containing the parameters passed:
            request.SetPointValue (Set Point Value): The Set Point value to write
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: The return object defined for the command with the following fields:
            EmptyResponse (Empty Response): An empty response data type used if no response is required.
        """

        logging.debug("WriteSetPoint called in {current_mode} mode".format(
            current_mode=('simulation' if self.simulation_mode else 'real')))

        try:
            controller = self._get_channel(context.invocation_metadata(),
                                           "Command")
            return self.implementation.WriteSetPoint(request, controller,
                                                     context)
        except (SiLAError, DeviceError, DecoratorInvalidChannelIndex) as err:
            if isinstance(err, QmixSDKSiLAError):
                err = QmixSDKSiLAError(err)
            elif isinstance(err, DecoratorInvalidChannelIndex):
                err = InvalidChannelIndexError(
                    err.invalid_index,
                    f"The index has to be between 0 and {self.num_channels - 1}."
                )
            err.raise_rpc_error(context=context)
Example #2
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))
    def SwitchToPosition(self, request, context: grpc.ServicerContext) \
            -> ValvePositionController_pb2.SwitchToPosition_Responses:
        """
        Executes the unobservable command "Switch To Position"
            Switches the valve to the specified position. The given position has to be less than the NumberOfPositions or else a ValidationError is thrown.

        :param request: gRPC request containing the parameters passed:
            request.Position (Position): The target position that the valve should be switched to.
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: The return object defined for the command with the following fields:
            EmptyResponse (Empty Response): An empty response data type used if no response is required.
        """

        if not self.system.state.is_operational():
            raise SystemNotOperationalError('de.cetoni/valves/ValvePositionController/v1/Command/SwitchToPosition')

        valve = self._get_valve(context.invocation_metadata(), "Command")

        requested_valve_pos = request.Position.value
        num_of_valve_pos = valve.number_of_valve_positions()

        if requested_valve_pos < 0 or requested_valve_pos >= num_of_valve_pos:
            raise ValvePositionOutOfRangeError(
                f"The given position ({requested_valve_pos}) is not in the range "
                "for this valve. Adjust the valve position to fit in the range "
                f"between 0 and {num_of_valve_pos - 1}!"
            )

        valve.switch_valve_to_position(requested_valve_pos)
        return ValvePositionController_pb2.SwitchToPosition_Responses()
Example #4
0
    def SetOutputValue(self, request, context: grpc.ServicerContext) \
            -> AnalogOutChannelController_pb2.SetOutputValue_Responses:
        """
        Executes the unobservable command "Set Output Value"
            Set the value of the analog output channel.

        :param request: gRPC request containing the parameters passed:
            request.Value (Value): The value to set.
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: The return object defined for the command with the following fields:
            EmptyResponse (Empty Response): An empty response data type used if no response is required.
        """

        logging.debug("SetOutputValue called in {current_mode} mode".format(
            current_mode=('simulation' if self.simulation_mode else 'real')))

        try:
            channel: AnalogOutChannel = self._get_channel(
                context.invocation_metadata(), "Command")
            return self.implementation.SetOutputValue(request, channel,
                                                      context)
        except (SiLAError, DeviceError,
                DecoratorInvalidChannelIndexError) as err:
            if isinstance(err, DeviceError):
                err = QmixSDKSiLAError(err)
            elif isinstance(err, DecoratorInvalidChannelIndexError):
                err = InvalidChannelIndexError(
                    err.invalid_index,
                    f"The index has to be between 0 and {self.num_channels - 1}."
                )
            err.raise_rpc_error(context=context)
Example #5
0
    def Subscribe_ControllerValue(self, request, context: grpc.ServicerContext) \
            -> ControlLoopService_pb2.Subscribe_ControllerValue_Responses:
        """
        Requests the observable property Controller Value
            The actual value from the Device

        :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 stream with the following fields:
            ControllerValue (Controller Value): The actual value from the Device
        """

        logging.debug(
            "Property ControllerValue requested in {current_mode} mode".format(
                current_mode=(
                    'simulation' if self.simulation_mode else 'real')))
        try:
            controller = self._get_channel(context.invocation_metadata(),
                                           "Property")
            return self.implementation.Subscribe_ControllerValue(
                request, controller, context)
        except (SiLAError, DecoratorInvalidChannelIndex) as err:
            if isinstance(err, DecoratorInvalidChannelIndex):
                err = InvalidChannelIndexError(
                    err.invalid_index,
                    f"The index has to be between 0 and {self.num_channels - 1}."
                )
            err.raise_rpc_error(context=context)
Example #6
0
    def Subscribe_Value(self, request, context: grpc.ServicerContext) \
            -> AnalogOutChannelController_pb2.Subscribe_Value_Responses:
        """
        Requests the observable property Value
            The value of the analog output 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 stream with the following fields:
            Value (Value): The value of the analog output channel.
        """

        logging.debug("Property Value requested in {current_mode} mode".format(
            current_mode=('simulation' if self.simulation_mode else 'real')))
        try:
            channel = self._get_channel(context.invocation_metadata(),
                                        "Property")
            for value in self.implementation.Subscribe_Value(
                    request, channel, context):
                yield value
        except (SiLAError, DeviceError,
                DecoratorInvalidChannelIndexError) as err:
            if isinstance(err, DeviceError):
                err = QmixSDKSiLAError(err)
            elif isinstance(err, DecoratorInvalidChannelIndexError):
                err = InvalidChannelIndexError(
                    err.invalid_index,
                    f"The index has to be between 0 and {self.num_channels - 1}."
                )
            err.raise_rpc_error(context=context)
    def TogglePosition(self, request, context: grpc.ServicerContext) \
            -> ValvePositionController_pb2.TogglePosition_Responses:
        """
        Executes the unobservable command "Toggle Position"
            This command only applies for 2-way valves to toggle between its two different positions. If the command is called for any other valve type a ValveNotToggleable error is thrown.

        :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: The return object defined for the command with the following fields:
            EmptyResponse (Empty Response): An empty response data type used if no response is required.
        """

        if not self.system.state.is_operational():
            raise SystemNotOperationalError('de.cetoni/valves/ValvePositionController/v1/Command/TogglePosition')

        valve = self._get_valve(context.invocation_metadata(), "Command")

        if valve.number_of_valve_positions() > 2:
            raise ValveNotToggleableError()

        try:
            curr_pos = valve.actual_valve_position()
            valve.switch_valve_to_position((curr_pos + 1) % 2)

            return ValvePositionController_pb2.TogglePosition_Responses()
        except DeviceError as err:
            if err.errorcode == -2:
                raise ValvePositionNotAvailableError()
            raise err
Example #8
0
    def MoveToHomePosition(self, request, context: grpc.ServicerContext) \
            -> AxisPositionController_pb2.MoveToHomePosition_Responses:
        """
        Executes the unobservable command "Move To Home Position"
            Move the axis to its home position

        :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: The return object defined for the command with the following fields:
            EmptyResponse (Empty Response): An empty response data type used if no response is required.
        """

        axis = self._get_axis(context.invocation_metadata())
        axis_name = axis.get_device_name()
        axis.find_home()

        is_moving = True
        while is_moving:
            time.sleep(0.5)
            logging.info("Position: %s (axis: %s)", axis.get_actual_position(),
                         axis_name)
            is_moving = not axis.is_homing_position_attained()
        logging.info(f"MoveToHomePosition for {axis_name} done")

        return AxisPositionController_pb2.MoveToHomePosition_Responses()
Example #9
0
    def StopControlLoop(self, request, context: grpc.ServicerContext) \
            -> ControlLoopService_pb2.StopControlLoop_Responses:
        """
        Executes the unobservable command "Stop Control Loop"
            Stops the Control Loop (has no effect, if no Loop is currently running)

        :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: The return object defined for the command with the following fields:
            EmptyResponse (Empty Response): An empty response data type used if no response is required.
        """

        logging.debug("StopControlLoop called in {current_mode} mode".format(
            current_mode=('simulation' if self.simulation_mode else 'real')))

        try:
            controller = self._get_channel(context.invocation_metadata(),
                                           "Command")
            return self.implementation.StopControlLoop(request, controller,
                                                       context)
        except (SiLAError, DecoratorInvalidChannelIndex) as err:
            if isinstance(err, DecoratorInvalidChannelIndex):
                err = InvalidChannelIndexError(
                    err.invalid_index,
                    f"The index has to be between 0 and {self.num_channels - 1}."
                )
            err.raise_rpc_error(context=context)
    def Subscribe_Position(self, request, context: grpc.ServicerContext) \
            -> ValvePositionController_pb2.Subscribe_Position_Responses:
        """
        Requests the observable property Position
            The current logical valve position. This is a value between 0 and NumberOfPositions - 1.

        :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 logical valve position. This is a value between 0 and NumberOfPositions - 1.
        """

        valve = self._get_valve(context.invocation_metadata(), "Property")

        new_valve_position = self._get_valve_position(valve)
        valve_position = new_valve_position + 1 # force sending the first value
        while not self.system.state.shutting_down():
            if self.system.state.is_operational():
                new_valve_position = self._get_valve_position(valve)
            if new_valve_position != valve_position:
                valve_position = new_valve_position
                yield ValvePositionController_pb2.Subscribe_Position_Responses(
                    Position=silaFW_pb2.Integer(value=valve_position)
                )
            # we add a small delay to give the client a chance to keep up.
            time.sleep(0.1)
 def wrapped_set_logging_context(self, request, context: grpc.ServicerContext):
     obj = request_log_context_extractors[type(request)](request)
     meta = {}
     for md in context.invocation_metadata():
         meta[md.key] = md.value
     obj["meta"] = meta
     obj["peer"] = context.peer()
     slogging.set_context(obj)
     self._log.info("new %s", type(request).__name__)
     return func(self, request, context)
Example #12
0
    def wrapper(self, request, context: ServicerContext):
        metadata = context.invocation_metadata()
        token = next((x for x in metadata if x[0] == config.token_header_key),
                     None)
        if token is None:
            context.abort(StatusCode.UNAUTHENTICATED, "Permission denied")

        token = token.value.split(token_header)[1]
        claims = get_token_claims(token)
        if claims is None:
            context.abort(StatusCode.UNAUTHENTICATED, "Permission denied")
        else:
            return func(self, request, context, claims=claims)
Example #13
0
 def initialize_context(self, context: grpc.ServicerContext, *args,
                        **kwargs):
     """
     Returns the initial request object.
     """
     # parser_context = cls.get_parser_context(request)
     request = HttpRequest()
     request.META = dict(context.invocation_metadata())
     r = Request(
         request,
         authenticators=self.get_authenticators(),
         # negotiator=self.get_content_negotiator(),
         # parser_context=parser_context
     )
     context.user = r.user
     self.check_permissions(r, context)
     return context
Example #14
0
    def Get_MaximumPosition(self, request, context: grpc.ServicerContext) \
            -> AxisPositionController_pb2.Get_MaximumPosition_Responses:
        """
        Requests the unobservable property Maximum Position
            The maximum position limit of an axis

        :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:
            MaximumPosition (Maximum Position): The maximum position limit of an axis
        """

        axis = self._get_axis(context.invocation_metadata())

        return AxisPositionController_pb2.Get_MaximumPosition_Responses(
            MaximumPosition=silaFW_pb2.Real(value=axis.get_position_max()))
Example #15
0
    def StopMoving(self, request, context: grpc.ServicerContext) \
            -> AxisPositionController_pb2.StopMoving_Responses:
        """
        Executes the unobservable command "Stop Moving"
            Immediately stops axis movement of a single axis

        :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: The return object defined for the command with the following fields:
            EmptyResponse (Empty Response): An empty response data type used if no response is required.
        """

        axis = self._get_axis(context.invocation_metadata())
        axis.stop_move()

        return AxisPositionController_pb2.StopMoving_Responses()
    def Get_NumberOfPositions(self, request, context: grpc.ServicerContext) \
            -> ValvePositionController_pb2.Get_NumberOfPositions_Responses:
        """
        Requests the unobservable property Number Of Positions
            The number of the valve positions available.

        :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:
            NumberOfPositions (Number Of Positions): The number of the valve positions available.
        """

        valve = self._get_valve(context.invocation_metadata(), "Property")
        if valve not in self.valve_positions:
            self.valve_positions[valve] = valve.number_of_valve_positions()

        return ValvePositionController_pb2.Get_NumberOfPositions_Responses(
            NumberOfPositions=silaFW_pb2.Integer(value=self.valve_positions[valve])
        )
Example #17
0
    def Get_PositionUnit(self, request, context: grpc.ServicerContext) \
            -> AxisPositionController_pb2.Get_PositionUnit_Responses:
        """
        Requests the unobservable property PositionUnit
            The position unit used for specifying the position of an axis

        :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:
            PositionUnit (PositionUnit): The position unit used for specifying the position of an axis
        """

        axis = self._get_axis(context.invocation_metadata())
        unit = axis.get_position_unit()
        unit_str = (unit.prefix.name
                    if unit.prefix.name != 'unit' else '') + unit.unitid.name

        return AxisPositionController_pb2.Get_PositionUnit_Responses(
            PositionUnit=silaFW_pb2.String(value=unit_str))
Example #18
0
    def RunControlLoop(self, request, 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:
            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.
        """

        logging.debug("RunControlLoop called in {current_mode} mode".format(
            current_mode=('simulation' if self.simulation_mode else 'real')))

        try:
            controller: ControllerChannel = self._get_channel(
                context.invocation_metadata(), "Command")
            if controller.is_control_loop_enabled():
                raise SiLAFrameworkError(
                    error_type=SiLAFrameworkErrorType.
                    COMMAND_EXECUTION_NOT_ACCEPTED,
                    msg=
                    "There is a running control loop already. Cannot start a new loop!"
                )
            confirmation = self.implementation.RunControlLoop(
                request, controller, context)
            self.uuid_to_controller[
                confirmation.commandExecutionUUID.value] = controller
            return confirmation
        except (SiLAError, DecoratorInvalidChannelIndex) as err:
            if isinstance(err, DecoratorInvalidChannelIndex):
                err = InvalidChannelIndexError(
                    err.invalid_index,
                    f"The index has to be between 0 and {self.num_channels - 1}."
                )
            err.raise_rpc_error(context=context)
Example #19
0
    def GetSalesCount(
        self, request: product_insight_api_pb2.GetSalesCountRequest,
        context: grpc.ServicerContext
    ) -> product_insight_api_pb2.GetSalesCountResponse:
        # FIXME! Observability.
        # authorization
        client_access_token = ''
        for metadata in context.invocation_metadata():
            if 'authorization' == metadata.key:
                client_access_token = str(metadata.value).replace(
                    'Bearer ', '', 1)
        if client_access_token != self.access_token:
            context.set_code(grpc.StatusCode.UNAUTHENTICATED)
            return product_insight_api_pb2.GetSalesCountResponse()

        start_time = request.start_time  # type: timestamp_pb2.Timestamp
        end_time = request.end_time  # type: timestamp_pb2.Timestamp
        product_id = request.product_id  # type: int

        start_time_epoch = start_time.seconds + start_time.nanos / 1e9  # type: float
        end_time_epoch = end_time.seconds + end_time.nanos / 1e9  # type: float

        if product_id not in self.products_inverted_index:
            return product_insight_api_pb2.GetSalesCountResponse(
                product_id=product_id,
                sales_count=0,
            )

        sales_count = 0  # type: int

        sales_times = self.products_inverted_index[product_id]
        for t in sales_times:
            if start_time_epoch <= t <= end_time_epoch:
                sales_count += 1

        return product_insight_api_pb2.GetSalesCountResponse(
            product_id=product_id,
            sales_count=sales_count,
        )
Example #20
0
    def SetOutput(self, request, context: grpc.ServicerContext) \
            -> DigitalOutChannelController_pb2.SetOutput_Responses:
        """
        Executes the unobservable command "Set Output"
            Switch a digital output channel on or off.

        :param request: gRPC request containing the parameters passed:
            request.State (State): The state to set.
        :param context: gRPC :class:`~grpc.ServicerContext` object providing gRPC-specific information

        :returns: The return object defined for the command with the following fields:
            EmptyResponse (Empty Response): An empty response data type used if no response is required.
        """

        logging.debug("SetOutput called in {current_mode} mode".format(
            current_mode=('simulation' if self.simulation_mode else 'real')))

        # parameter validation
        # if request.my_paramameter.value out of scope :
        #        sila_val_err = SiLAValidationError(parameter="myParameter",
        #                                           msg=f"Parameter {request.my_parameter.value} out of scope!")
        #        sila_val_err.raise_rpc_error(context)

        try:
            channel = self._get_channel(context.invocation_metadata(),
                                        "Command")
            return self.implementation.SetOutput(request, channel, context)
        except (SiLAError, DeviceError,
                DecoratorInvalidChannelIndexError) as err:
            if isinstance(err, DeviceError):
                err = QmixSDKSiLAError(err)
            elif isinstance(err, DecoratorInvalidChannelIndexError):
                err = InvalidChannelIndexError(
                    err.invalid_index,
                    f"The index has to be between 0 and {self.num_channels - 1}."
                )
            err.raise_rpc_error(context=context)
Example #21
0
    def Subscribe_Position(self, request, context: grpc.ServicerContext) \
            -> AxisPositionController_pb2.Subscribe_Position_Responses:
        """
        Requests the observable property Position
            The current position of an axis

        :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 position of an axis
        """

        axis = self._get_axis(context.invocation_metadata())

        new_position = axis.get_actual_position()
        position = new_position + 1  # force sending the first value
        while not self.system.state.shutting_down():
            new_position = axis.get_actual_position()
            if not math.isclose(new_position, position):
                position = new_position
                yield AxisPositionController_pb2.Subscribe_Position_Responses(
                    Position=silaFW_pb2.Real(value=position))
            time.sleep(0.1)  # give client some time to catch up