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)
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()
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)
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)
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
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()
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)
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)
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
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()))
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]) )
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))
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)
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, )
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)
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