def _on_at_parking_sequence_position( self, parking_sequence_update: ParkingSequenceUpdate): """ If reached a sequence position then move sequence set point on to next position Only move if sequence reported agrees with sequence this manager set and haven't already reached the end of the sequence. Args: parking_sequence_update: update indicating that a parking sequence has ended """ if parking_sequence_update.parking_sequence == self.parking_index and self._parking_sequence_started: if self.get_is_in_beam(): # axes are unparking if self.parking_index == 0: self._move_axis_to(None) elif self.parking_index is not None: self._move_axis_to(self.parking_index - 1) else: self._parking_sequence_started = False else: # axes are parking if self.parking_index is None: STATUS_MANAGER.update_error_log( "Parking sequence error - the parking index is None but we are " "parking the axis, this should not be possible") STATUS_MANAGER.update_active_problems( ProblemInfo( "Next park sequence triggered but manager is in beam (report error)", "InBeamManager", AlarmSeverity.MINOR_ALARM)) if self.parking_index + 1 < self._maximum_sequence_count: self._move_axis_to(self.parking_index + 1) else: self._parking_sequence_started = False
def restore_pre_move_velocity(self): """ Restore the cached axis velocity. Restore the cached axis velocity from the value stored on the server and update the restored cache status. """ if self._velocity_restored: STATUS_MANAGER.update_error_log( "Velocity for {pv_name} has not been restored from cache. The cache has already been " "restored previously. Hint: Are you moving the axis outside of the reflectometry server?" .format(pv_name=self.name)) else: if self._velocity_to_restore is None: STATUS_MANAGER.update_error_log( "Cannot restore velocity: velocity cache is None for {pv_name}" .format(pv_name=self.name)) STATUS_MANAGER.update_active_problems( ProblemInfo("Unable to restore axis velocity", self.name, Severity.MINOR_ALARM)) else: logger.debug( "Restoring velocity cache of {value} for {pv_name}".format( value=self._velocity_to_restore, pv_name=self.name)) self._write_pv(self._velo_pv, self._velocity_to_restore) self._velocity_restored = True velocity_bool_autosave.write_parameter( self.name + "_velocity_restored", self._velocity_restored)
def _init_velocity_cache(self): """ Initialise the velocity cache for the current axis. Initialise the velocity cache and its restored status. If no cache exists then load the last velocity value from the auto-save file. """ autosave_value = velocity_float_autosave.read_parameter( self.name, None) if autosave_value is not None: logger.debug( "Restoring {pv_name} velocity_cache with auto-save value {value}" .format(pv_name=self.name, value=autosave_value)) self._velocity_to_restore = autosave_value else: logger.error( "Error: Unable to initialise velocity cache from auto-save for {}." .format(self.name)) if autosave_value is not None: autosave_value = velocity_bool_autosave.read_parameter( self.name + "_velocity_restored", None) if autosave_value is not None: logger.debug( "Restoring {pv_name} velocity_restored with auto-save value {value}" .format(pv_name=self.name, value=autosave_value)) self._velocity_restored = autosave_value else: STATUS_MANAGER.update_error_log( "Error: Unable to initialise velocity_restored flag from auto-save for {}." .format(self.name)) STATUS_MANAGER.update_active_problems( ProblemInfo( "Unable to read autosaved velocity_restored flag", self.name, Severity.MINOR_ALARM))
def _add_all_parameter_pvs(self): """ Add PVs for each beamline parameter in the reflectometry configuration to the server's PV database. """ param_info = {} for group in BeamlineParameterGroup: param_info[group] = [] align_info = [] for parameter in self._beamline.parameters.values(): try: param_info_record = self._add_parameter_pvs(parameter) if param_info_record is not None: for group in parameter.group_names: param_info[group].append(param_info_record) if parameter.define_current_value_as is not None: align_info_record = { "name": param_info_record["name"], "prepended_alias": param_info_record["prepended_alias"], "type": "align", "description": "" } align_info.append(align_info_record) except Exception as err: STATUS_MANAGER.update_error_log("Error adding PV for parameter {}: {}".format(parameter.name, err), err) STATUS_MANAGER.update_active_problems( ProblemInfo("Error adding parameter PV", parameter.name, Severity.MAJOR_ALARM)) for param_group in BeamlineParameterGroup: self._add_pv_with_fields(PARAM_INFO_LOOKUP[param_group], None, STANDARD_2048_CHAR_WF_FIELDS, BeamlineParameterGroup.description(param_group), None, value=compress_and_hex(json.dumps(param_info[param_group]))) self._add_pv_with_fields(ALIGN_INFO, None, STANDARD_2048_CHAR_WF_FIELDS, "All alignment pvs information", None, value=compress_and_hex(json.dumps(align_info)))
def correction(self, setpoint): """ Correction as interpolated from the grid data provided Args: setpoint: setpoint to use to calculate correction Returns: the correction calculated using the grid data. """ self.set_point_value_as_parameter.sp_rbv = setpoint evaluation_point = [ param.sp_rbv for param in self._beamline_parameters ] if None in evaluation_point: non_initialised_params = [ param.name for param in self._beamline_parameters if param.sp_rbv is None ] STATUS_MANAGER.update_error_log( "Engineering correction, '{}', evaluated for non-autosaved value, {}" .format(self.description, non_initialised_params)) STATUS_MANAGER.update_active_problems( ProblemInfo( "Engineering correction evaluated for non-autosaved value", self.description, Severity.MINOR_ALARM)) interpolated_value = [0.0] else: interpolated_value = griddata(self._grid_data_provider.points, self._grid_data_provider.corrections, evaluation_point, 'linear', self._default_correction) return interpolated_value[0]
def optional_is_set(optional_id, macros): """ Check whether an optional macro for use in the configuration is set or not. Args: optional_id (String): The ID of the optional macro macros (dict): The macro values passed into the reflectometry server Returns: True if macro with given ID is set to True, otherwise False """ try: macro_name = "OPTIONAL_{}".format(optional_id) macro_value = macros[macro_name] if macro_value.lower() == "true": return True elif macro_value.lower() == "false": return False else: STATUS_MANAGER.update_error_log( ("Invalid value for IOC macro {}: {} (Expected: True/False)".format(macro_name, macro_value))) STATUS_MANAGER.update_active_problems( ProblemInfo("Invalid IOC macro value", macro_name, Severity.MINOR_ALARM)) return False except KeyError: return False
def _log_autosave_type_error(self): """ Logs an error that the autosave value this parameter was trying to read was of the wrong type. """ STATUS_MANAGER.update_error_log( "Could not read autosave value for parameter {}: unexpected type.".format(self.name)) STATUS_MANAGER.update_active_problems(ProblemInfo("Parameter autosave value has unexpected type", self.name, Severity.MINOR_ALARM))
def _check_and_move_component(self): """ Checks whether this parameter is initialised and moves the underlying component to its setpoint if so. """ if self._set_point_rbv is not None: self._move_component() else: STATUS_MANAGER.update_active_problems( ProblemInfo("No parameter initialization value found", self.name, Severity.MAJOR_ALARM)) raise ParameterNotInitializedException(self.name)
def velocity(self, value): """ Writes a value to the underlying velocity PV's VAL field. Args: value (float): The value to set """ STATUS_MANAGER.update_error_log( "Error: An attempt was made to write a velocity to a Jaws Axis. We do not " "support this as we do not expect jaws to be synchronised.") STATUS_MANAGER.update_active_problems( ProblemInfo( "Drivers for Jaws axes should not be synchronised. Check configuration", self.name, Severity.MINOR_ALARM))
def _set_motor_moving_pv(self, value): """ Set/clear the motor is moving pv to indicate we are calculating the readback value Args: value: value to set it to. 1 is moving, 0 not moving """ try: ChannelAccess.caput(MOTOR_MOVING_PV, value, safe_not_quick=False) except Exception as e: STATUS_MANAGER.update_error_log( "Failed to set motor moving pv: {}".format(e), e) STATUS_MANAGER.update_active_problems( ProblemInfo("Failed to update motor moving pv", "pv_wrapper", Severity.MAJOR_ALARM))
def define_position_as(self, new_position): """ Set the current position in the underlying hardware as the new position without moving anything Args: new_position: position to move to """ try: with motor_in_set_mode(self._prefixed_pv): self._write_pv_with_retry(self._sp_pv, new_position) except ValueError as ex: STATUS_MANAGER.update_error_log( "Can not define zero: {}".format(ex), ex) STATUS_MANAGER.update_active_problems( ProblemInfo("Failed to redefine position", self.name, Severity.MINOR_ALARM))
def read(self, reason): """ Processes an incoming caget request. Args: reason (str): The PV that is being read. Returns: The value associated to this PV """ try: if self._initialised: if self._pv_manager.is_param(reason): return self.getParam(reason) elif self._pv_manager.is_beamline_mode(reason): return self._beamline_mode_value(self._beamline.active_mode) elif is_pv_name_this_field(BEAMLINE_MOVE, reason): return self._beamline.move elif is_pv_name_this_field(REAPPLY_MODE_INITS, reason): return self._beamline.reinit_mode_on_move elif is_pv_name_this_field(SERVER_STATUS, reason): beamline_status_enums = self._pv_manager.PVDB[SERVER_STATUS]["enums"] new_value = beamline_status_enums.index(STATUS_MANAGER.status.display_string) # Set the value so that the error condition is set self.setParam(reason, new_value) return new_value elif is_pv_name_this_field(SERVER_MESSAGE, reason): return STATUS_MANAGER.message elif is_pv_name_this_field(SERVER_ERROR_LOG, reason): return STATUS_MANAGER.error_log elif is_pv_name_this_field(SAMPLE_LENGTH, reason): return self._footprint_manager.get_sample_length() elif self._pv_manager.is_alarm_status(reason): return self.getParamDB(self._pv_manager.strip_fields_from_pv(reason)).alarm elif self._pv_manager.is_alarm_severity(reason): return self.getParamDB(self._pv_manager.strip_fields_from_pv(reason)).severity except Exception as e: STATUS_MANAGER.update_error_log("Exception when reading parameter {}".format(reason), e) STATUS_MANAGER.update_active_problems( ProblemInfo("PV Value read caused exception.", reason, Severity.MAJOR_ALARM)) return return self.getParam(reason)
def _create_beamline_in_error(error_message): """ Args: error_message: error message to set for beamline Returns: a blank beamline with an error status set """ error_mode = BeamlineMode("No modes", []) beamline = Beamline([], [], [], [error_mode]) try: STATUS_MANAGER.update_active_problems( ProblemInfo( "Error reading configuration: {}".format(error_message), "Configuration", Severity.MAJOR_ALARM)) except Exception as e: print(e) return beamline
def _get_component_sp_and_is_to_from_parking( self, for_correction=False) -> Tuple[Optional[float], bool]: """ Args: for_correction (bool): Setpoint is for engineering correction Returns: position that the set point axis is set to or None if it should not move because it is in a parking sequence with a None in it whether the movement is from or to a park position """ if not self.component.beam_path_set_point.axis[self.component_axis].is_in_beam \ and self._out_of_beam_lookup is None: STATUS_MANAGER.update_error_log( "An axis for component {} is set to out of beam but there is no out of beam position defined " "for the driver for the associated motor axis {}".format( self.component.name, self._motor_axis.name)) STATUS_MANAGER.update_active_problems( ProblemInfo("No out of beam position defined for motor_axis", self.name, Severity.MINOR_ALARM)) parking_index = self.component.beam_path_set_point.in_beam_manager.parking_index if parking_index is None or self._out_of_beam_lookup is None: displacement = self.component.beam_path_set_point.axis[ self.component_axis].get_displacement() is_to_from_park = not self.component.beam_path_rbv.axis[ self.component_axis].is_in_beam else: is_to_from_park = True beam_interception = self.component.beam_path_set_point.calculate_beam_interception( ) out_of_beam_position = self._out_of_beam_lookup.get_position_for_intercept( beam_interception) if out_of_beam_position.is_offset: displacement = self.component.beam_path_set_point.axis[self.component_axis].\ get_displacement_for(out_of_beam_position.get_sequence_position(parking_index)) else: displacement = out_of_beam_position.get_sequence_position( parking_index) if displacement is None and for_correction: displacement = out_of_beam_position.get_final_position() return displacement, is_to_from_park
def _run_custom_function(self, new_sp, original_sp): """ Run the users custom function attached to this parameter Args: new_sp: the setpoint that has just been set original_sp: the value of the setpoint before the move was called """ logger.debug(f"Running custom function on parameter {self.name} ...") try: message = self._custom_function(new_sp, original_sp) logger.debug(f"... Finished running custom function on parameter {self.name} it returned: {message}") if message is not None: STATUS_MANAGER.update_error_log(f"Custom function on parameter {self.name} returned: {message}") STATUS_MANAGER.update_active_problems( ProblemInfo(f"Custom function returned: {message}", self.name, Severity.NO_ALARM)) except Exception as ex: STATUS_MANAGER.update_error_log(f"Custom function on parameter {self.name} failed with {ex}") STATUS_MANAGER.update_active_problems( ProblemInfo("Custom function on parameter failed.", self.name, Severity.MAJOR_ALARM))
def _initialise_sp_from_motor(self, _): """ Get the setpoint value for this parameter based on the motor setpoint position. """ if not self.component.beam_path_set_point.in_beam_manager.get_is_in_beam(): autosave_val = self.component.beam_path_set_point.axis[self.axis].autosaved_value if autosave_val is not None: init_sp = autosave_val else: # If the axis is out of the beam and there is not autosave the displacement should be set to 0 init_sp = 0.0 STATUS_MANAGER.update_error_log("Parameter {} is parkable so should have an autosave value but " "doesn't. Has been set to 0 check its value".format(self.name)) STATUS_MANAGER.update_active_problems( ProblemInfo("Parameter has no autosave value", self.name, Severity.MAJOR_ALARM)) self.component.beam_path_set_point.axis[self.axis].set_relative_to_beam(init_sp) else: init_sp = self.component.beam_path_set_point.axis[self.axis].get_relative_to_beam() self._set_initial_sp(init_sp)
def _move_drivers(self): """ Issue move for all drivers at the speed of the slowest axis and set appropriate status for failure/success. """ try: self._perform_move_for_all_drivers(self._get_max_move_duration()) except ZeroDivisionError as e: STATUS_MANAGER.update_error_log( "Failed to perform beamline move: {}".format(e), e) STATUS_MANAGER.update_active_problems( ProblemInfo("Failed to move driver", "beamline", Severity.MAJOR_ALARM)) return except (ValueError, UnableToConnectToPVException) as e: STATUS_MANAGER.update_error_log( "Unable to connect to PV: {}".format(str(e))) STATUS_MANAGER.update_active_problems( ProblemInfo("Unable to connect to PV", "beamline", Severity.MAJOR_ALARM)) return
def write(self, reason, value): """ Process an incoming channel_access request. :param reason: The PV that is being written to. :param value: The value being written to the PV """ value_accepted = True try: if self._pv_manager.is_param(reason): value_accepted = self._driver_help.param_write(reason, value) elif is_pv_name_this_field(BEAMLINE_MOVE, reason): self._beamline.move = 1 elif is_pv_name_this_field(REAPPLY_MODE_INITS, reason): self._beamline.reinit_mode_on_move = value elif self._pv_manager.is_beamline_mode(reason): try: beamline_mode_enums = self._pv_manager.PVDB[BEAMLINE_MODE]["enums"] new_mode_name = beamline_mode_enums[value] self._beamline.active_mode = new_mode_name except ValueError: STATUS_MANAGER.update_error_log("Invalid value entered for mode. (Possible modes: {})".format( ",".join(self._beamline.mode_names))) value_accepted = False elif is_pv_name_this_field(SAMPLE_LENGTH, reason): self._footprint_manager.set_sample_length(value) else: STATUS_MANAGER.update_error_log("Error: PV is read only") value_accepted = False if value_accepted: pv_name = "{}{}".format(REFLECTOMETRY_PREFIX, reason) self.put_log.write_pv_put(pv_name, value, self.getParam(reason)) self._update_param_both_pv_and_pv_val(reason, value) self.update_monitors() except Exception as e: STATUS_MANAGER.update_error_log("PV Value {} for PV {} rejected by server: {}".format(value, reason, e), e) STATUS_MANAGER.update_active_problems(ProblemInfo("PV Value rejected by server.", reason, Severity.MINOR_ALARM)) value_accepted = False return value_accepted
def _move_for_all_beamline_parameters(self): """ Updates the beamline parameters to the latest set point value; reapplies if they are in the mode. Then moves to latest positions. """ logger.info("BEAMLINE MOVE TRIGGERED") parameters = self._beamline_parameters.values() parameters_in_mode = self._active_mode.get_parameters_in_mode( parameters, None) for beamline_parameter in parameters: if beamline_parameter in parameters_in_mode or beamline_parameter.sp_changed: try: beamline_parameter.move_to_sp_no_callback() except ParameterNotInitializedException: STATUS_MANAGER.update_active_problems( ProblemInfo( "Parameter not initialized. Is the configuration correct?", beamline_parameter.name, Severity.MAJOR_ALARM)) return self._move_drivers()
def _add_constants_pvs(self): """ Add pvs for the beamline constants """ beamline_constant_info = [] for beamline_constant in self._beamline.beamline_constants: try: const_alias = create_pv_name(beamline_constant.name, list(self.PVDB.keys()), CONST_PREFIX, limit=20, allow_colon=True) prepended_alias = "{}:{}".format(CONST_PREFIX, const_alias) if isinstance(beamline_constant.value, bool): value = 1 if bool(beamline_constant.value) else 0 fields = PARAM_FIELDS_BINARY elif isinstance(beamline_constant.value, str): value = beamline_constant.value fields = STANDARD_2048_CHAR_WF_FIELDS else: value = float(beamline_constant.value) fields = STANDARD_FLOAT_PV_FIELDS self._add_pv_with_fields(prepended_alias, None, fields, beamline_constant.description, None, interest="MEDIUM", value=value) logger.info("Adding Constant {} with value {}".format(beamline_constant.name, beamline_constant.value)) beamline_constant_info.append( {"name": beamline_constant.name, "prepended_alias": prepended_alias, "type": "float_value", "description": beamline_constant.description}) except Exception as err: STATUS_MANAGER.update_error_log("Error adding constant {}: {}".format(beamline_constant.name, err), err) STATUS_MANAGER.update_active_problems( ProblemInfo("Error adding PV for beamline constant", beamline_constant.name, Severity.MAJOR_ALARM)) self._add_pv_with_fields(BEAMLINE_CONSTANT_INFO, None, STANDARD_2048_CHAR_WF_FIELDS, "All value parameters", None, value=compress_and_hex(json.dumps(beamline_constant_info)))
def on_define_position(self, define_position: DefineValueAsEvent): """ When a define position happens on the rbv control parameters take it and apply it to the motor axes after transforming the parameters by getting their readback values and setting the newly set parameter. Args: define_position: the position defined for the axis """ rbv_axis = self.beam_path_rbv.axis height, pivot_angle, seesaw = self._calculate_motor_rbvs(rbv_axis) change_axis = define_position.change_axis if change_axis == self._position_axis: height = define_position.new_position elif change_axis == self._angle_axis: pivot_angle = define_position.new_position elif change_axis == ChangeAxis.SEESAW: seesaw = define_position.new_position else: STATUS_MANAGER.update_error_log( "Define on bench using axis {} is not allowed".format( change_axis)) STATUS_MANAGER.update_active_problems( ProblemInfo("Invalid bench update axis", self.name, Severity.MINOR_ALARM)) front_jack_height, rear_jack_height, horizontal_position = \ self._calculate_motor_positions(height, pivot_angle, seesaw) rbv_axis[ChangeAxis.JACK_FRONT].define_axis_position_as( front_jack_height) rbv_axis[ChangeAxis.JACK_REAR].define_axis_position_as( rear_jack_height) if change_axis is not self._position_axis: rbv_axis[ChangeAxis.SLIDE].define_axis_position_as( horizontal_position)
def correction(self, setpoint): """ Correction as calculated by the provided user function. Args: setpoint: setpoint to use to calculate correction Returns: the correction calculated using the users function. """ try: return self._user_correction_function( setpoint, *[param.sp_rbv for param in self._beamline_parameters]) except Exception as ex: if setpoint is None or None in [ param.sp_rbv for param in self._beamline_parameters ]: non_initialised_params = [ param.name for param in self._beamline_parameters if param.sp_rbv is None ] STATUS_MANAGER.update_error_log( "Engineering correction, '{}', raised exception '{}' is this because you have not coped with " "non-autosaved value, {}".format(self.description, ex, non_initialised_params), ex) STATUS_MANAGER.update_active_problems( ProblemInfo( "Invalid engineering correction (uses non autosaved value?)", self.description, Severity.MINOR_ALARM)) else: STATUS_MANAGER.update_error_log( "Engineering correction, '{}', raised exception '{}' ". format(self.description, ex), ex) STATUS_MANAGER.update_active_problems( ProblemInfo("Engineering correction throws exception", self.description, Severity.MINOR_ALARM)) return 0
def define_position_as(self, new_position): """ Set the current position in the underlying hardware as the new position without moving anything Args: new_position: position to move to """ try: mtr1, mtr2 = self._pv_names_for_directions("MTR") logger.info( "Defining position for axis {name} to {corrected_value}. " "From sp {sp} and rbv {rbv}.".format( name=self.name, corrected_value=new_position, sp=self.sp, rbv=self.rbv)) for motor in self._pv_names_for_directions("MTR"): rbv = self._read_pv("{}.RBV".format(motor)) sp = self._read_pv("{}".format(motor)) logger.info( " Motor {name} initially at rbv {rbv} sp {sp}".format( name=motor, rbv=rbv, sp=sp)) with motor_in_set_mode(mtr1), motor_in_set_mode(mtr2): # Ensure the position has been redefined before leaving the "Set" context, otherwise the motor moves self._write_pv_with_retry(self._sp_pv, new_position) for motor in self._pv_names_for_directions("MTR"): rbv = self._read_pv("{}.RBV".format(motor)) sp = self._read_pv("{}".format(motor)) logger.info( " Motor {name} moved to rbv {rbv} sp {sp}".format( name=motor, rbv=rbv, sp=sp)) except ValueError as ex: STATUS_MANAGER.update_error_log( "Can not define zero: {}".format(ex), ex) STATUS_MANAGER.update_active_problems( ProblemInfo("Failed to redefine position", self.name, Severity.MAJOR_ALARM))
def _initialise_sp_from_motor(self, _): # Optional parameters should always be autosaved and should not be initialised from the motor there is no code # to perform this operation so this is a major error if triggered. STATUS_MANAGER.update_error_log("Optional parameter, {}, was asked up init from motor".format(self.name)) STATUS_MANAGER.update_active_problems(ProblemInfo("Optional Parameter updating from motor", self.name, Severity.MAJOR_ALARM))
def __init__( self, component: 'Component', component_axis: ChangeAxis, motor_axis: PVWrapper, out_of_beam_positions: Optional[List[OutOfBeamPosition]] = None, synchronised: bool = True, engineering_correction: Optional[EngineeringCorrection] = None, pv_wrapper_for_parameter: Optional[PVWrapperForParameter] = None): """ Drive the IOC based on a component Args: component: Component for IOC driver motor_axis: The PV that this driver controls. out_of_beam_positions: Provides the out of beam status as configured for this pv_wrapper. synchronised: If True then axes will set their velocities so they arrive at the end point at the same time; if false they will move at their current speed. engineering_correction: the engineering correction to apply to the value from the component before it is sent to the pv. None for no correction pv_wrapper_for_parameter: change the pv wrapper based on the value of a parameter """ self.component = component self.component_axis = component_axis self._default_motor_axis = motor_axis self._motor_axis = None self._set_motor_axis(motor_axis, False) self._pv_wrapper_for_parameter = pv_wrapper_for_parameter if pv_wrapper_for_parameter is not None: pv_wrapper_for_parameter.parameter.add_listener( ParameterSetpointReadbackUpdate, self._on_parameter_update) if out_of_beam_positions is None: self._out_of_beam_lookup = None else: try: self._out_of_beam_lookup = OutOfBeamLookup( out_of_beam_positions) self.component.beam_path_rbv.axis[component_axis].park_sequence_count = \ self._out_of_beam_lookup.get_max_sequence_count() self.component.beam_path_set_point.axis[component_axis].park_sequence_count = \ self._out_of_beam_lookup.get_max_sequence_count() self.component.beam_path_set_point.axis[ component_axis].add_listener( ParkingSequenceUpdate, self._move_to_parking_sequence) except ValueError as e: STATUS_MANAGER.update_error_log(str(e)) STATUS_MANAGER.update_active_problems( ProblemInfo("Invalid Out Of Beam Positions", self.name, Severity.MINOR_ALARM)) self._synchronised = synchronised if engineering_correction is None: self._engineering_correction = NoCorrection() self.has_engineering_correction = False else: self.has_engineering_correction = True self._engineering_correction = engineering_correction self._engineering_correction.add_listener( CorrectionUpdate, self._on_correction_update) self._engineering_correction.add_listener( CorrectionRecalculate, self._retrigger_motor_axis_updates) self._sp_cache = None self._rbv_cache = self._engineering_correction.from_axis( self._motor_axis.rbv, self._get_component_sp(True)) self.component.beam_path_rbv.axis[component_axis].add_listener( DefineValueAsEvent, self._on_define_value_as)