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 _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 param_write(self, pv_name, value): """ Write a parameter value from epics to the beamline. Args: pv_name: pv name value: value to set Returns: True if value was accepted by the system; False otherwise """ value_accepted = True param_name, param_sort = self._pv_manager.get_param_name_and_sort_from_pv( pv_name) param = self._beamline.parameter(param_name) if param_sort == PvSort.ACTION and not param.is_disabled: param.move = 1 elif param_sort == PvSort.SP and not param.is_disabled: param.sp = convert_from_epics_pv_value( param.parameter_type, value, self._pv_manager.PVDB[pv_name]) elif param_sort == PvSort.SET_AND_NO_ACTION: param.sp_no_move = convert_from_epics_pv_value( param.parameter_type, value, self._pv_manager.PVDB[pv_name]) elif param_sort == PvSort.DEFINE_POS_AS: param.define_current_value_as.new_value = convert_from_epics_pv_value( param.parameter_type, value, self._pv_manager.PVDB[pv_name]) else: STATUS_MANAGER.update_error_log( "Error: PV {} is read only".format(pv_name)) value_accepted = False return value_accepted
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 _initialise_mode(self, modes): """ Tries to read and apply the last active mode from autosave file. Defaults to first mode in list if unsuccessful. Args: modes(list[BeamlineMode]): A list of all the modes in this configuration. """ mode_name = mode_autosave.read_parameter(MODE_KEY, default=None) initial_mode = None try: initial_mode = self._modes[mode_name] except KeyError: STATUS_MANAGER.update_error_log( "Mode {} not found in configuration. Setting default.".format( mode_name)) if len(modes) > 0: initial_mode = modes[0] else: STATUS_MANAGER.update_error_log( "No modes have been configured.") if initial_mode is not None: self._active_mode = initial_mode self.trigger_listeners(ActiveModeUpdate(self._active_mode))
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 _block_until_pv_available(self): """ Blocks the process until the PV this driver is pointing at is available. """ while not self._ca.pv_exists(self._rbv_pv): STATUS_MANAGER.update_error_log( "{} does not exist. Check the PV is correct and the IOC is running. Retrying in {} s." .format(self._rbv_pv, RETRY_INTERVAL)) time.sleep(RETRY_INTERVAL)
def active_mode(self): """ Returns: the name of the current modes; None for no active mode """ try: return self._active_mode.name except AttributeError: STATUS_MANAGER.update_error_log( "Error: No active beamline mode found.") return None
def move(self, _): """ Move to all the beamline parameters in the mode or that have changed Args: _: dummy can be anything """ STATUS_MANAGER.clear_all() if self.reinit_mode_on_move: self._init_params_from_mode() self._move_for_all_beamline_parameters()
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 _validate(self, beamline_parameters, modes, drivers): errors = [] beamline_parameters_names = [ beamline_parameter.name for beamline_parameter in beamline_parameters ] for name in beamline_parameters_names: if beamline_parameters_names.count(name) > 1: errors.append( "Beamline parameters must be uniquely named. Duplicate '{}'" .format(name)) mode_names = [mode.name for mode in modes] for mode in mode_names: if mode_names.count(mode) > 1: errors.append( "Mode must be uniquely named. Duplicate '{}'".format(mode)) # Validate the order of long_axis and position axis_params = [ param for param in beamline_parameters if isinstance(param, AxisParameter) ] axes_per_comp = defaultdict(list) [ axes_per_comp[param.component.name].append(param.axis) for param in axis_params ] errors.extend( check_long_axis_before_position(axes_per_comp, "parameter")) axes_per_comp = defaultdict(list) [ axes_per_comp[driver.component.name].append(driver.component_axis) for driver in drivers ] errors.extend(check_long_axis_before_position(axes_per_comp, "driver")) for mode in modes: errors.extend(mode.validate(self._beamline_parameters.keys())) for parameter in self._beamline_parameters.values(): errors.extend(parameter.validate(self._drivers)) if len(errors) > 0: STATUS_MANAGER.update_error_log( "Beamline configuration is invalid:\n {}".format( "\n ".join(errors))) raise BeamlineConfigurationInvalidException( "Beamline configuration invalid: {}".format(";".join(errors)))
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 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 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_pv(self, pv): """ Read the value from a given PV. Args: pv (String): The pv to read """ value = self._ca.caget(pv) if value is not None: return value else: STATUS_MANAGER.update_error_log( "Could not connect to PV {}.".format(pv)) raise UnableToConnectToPVException( pv, "Check configuration is correct and IOC is running.")
def _strip_source_pv(self, pv): """ Extracts the direction key from a given pv. Params: pv (String): The source pv Returns: The direction key embedded within the pv """ for key in self._directions: if key in pv: return key STATUS_MANAGER.update_error_log( "Wrapper for {} received event from unexpected source: {}".format( self.name, pv))
def get_from_parameter(self, parameter, pv_fields): """ Get the value of the correct sort from a parameter Args: parameter(ReflectometryServer.parameters.BeamlineParameter): the parameter to get the value from pv_fields: values that the pv can take if Returns: the value of the parameter of the correct sort and their alarms """ severity = AlarmSeverity.No status = AlarmStatus.No if self == PvSort.SP: value, severity, status = _convert_to_epics_pv_value( parameter.parameter_type, parameter.sp, AlarmSeverity.No, AlarmStatus.No, pv_fields) elif self == PvSort.SP_RBV: value, severity, status = _convert_to_epics_pv_value( parameter.parameter_type, parameter.sp_rbv, AlarmSeverity.No, AlarmStatus.No, pv_fields) elif self == PvSort.RBV: value, severity, status = _convert_to_epics_pv_value( parameter.parameter_type, parameter.rbv, parameter.alarm_severity, parameter.alarm_status, pv_fields) elif self == PvSort.SET_AND_NO_ACTION: value, severity, status = _convert_to_epics_pv_value( parameter.parameter_type, parameter.sp_no_move, AlarmSeverity.No, AlarmStatus.No, pv_fields) elif self == PvSort.CHANGED: value = parameter.sp_changed elif self == PvSort.ACTION: value = parameter.move elif self == PvSort.CHANGING: value = parameter.is_changing elif self == PvSort.RBV_AT_SP: value = parameter.rbv_at_sp elif self == PvSort.DEFINE_POS_AS: if parameter.define_current_value_as is None: value, severity, status = float( "NaN"), AlarmSeverity.Invalid, AlarmStatus.UDF else: value = parameter.define_current_value_as.new_value else: value, severity, status = float( "NaN"), AlarmSeverity.Invalid, AlarmStatus.UDF STATUS_MANAGER.update_error_log( "PVSort not understood {}".format(PvSort)) return value, severity, status
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 _monitor_pv(self, pv, call_back_function): """ Adds a monitor function to a given PV. Args: pv (String): The pv to monitor call_back_function: The function to execute on a pv value change """ while True: if self._ca.pv_exists(pv): self._ca.add_monitor(pv, call_back_function) logger.debug("Monitoring {} for changes.".format(pv)) break else: STATUS_MANAGER.update_error_log( "Error adding monitor to {}: PV does not exist. Check the PV is correct and the IOC is running. " "Retrying in {} s.".format(pv, RETRY_INTERVAL)) time.sleep(RETRY_INTERVAL)
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 _convert_to_epics_pv_value(parameter_type: BeamlineParameterType, value: Union[float, int, str, bool], alarm_severity: AlarmSeverity, alarm_status: AlarmStatus, pv_fields: Dict): """ Convert from parameter value to the epic pv value Args: parameter_type: parameters type value: value to convert alarm_severity: alarm severity alarm_status: alarm status pv_fields: pv fields for the pv, e.g. used for options on en enum field Returns: epics value and alarms """ if alarm_status is None: status = alarm_status else: status = min(MAX_ALARM_ID, alarm_status) severity = alarm_severity if parameter_type == BeamlineParameterType.IN_OUT: if value: pv_value = OUT_IN_ENUM_TEXT.index("IN") else: pv_value = OUT_IN_ENUM_TEXT.index("OUT") elif parameter_type == BeamlineParameterType.ENUM: try: pv_value = pv_fields.get("enums", None).index(value) except ValueError: pv_value = -1 status = AlarmStatus.State severity = AlarmSeverity.Invalid STATUS_MANAGER.update_error_log( "Value set of parameter which is not in pv options {}".format( value)) else: if value is None: pv_value = float("NaN") else: pv_value = value return pv_value, severity, status
def _move_for_single_beamline_parameters(self, request: RequestMoveEvent): """ Moves starts from a single beamline parameter and move is to parameters sp read backs. If the request source is not in the mode then don't update any other parameters. Move to latest position. Args: request: request to move a single parameter; if source is None start from the beginning, otherwise start from source """ STATUS_MANAGER.clear_all() logger.info("PARAMETER MOVE TRIGGERED (source: {})".format( request.source.name)) if self._active_mode.has_beamline_parameter(request.source): parameters = self._beamline_parameters.values() parameters_in_mode = self._active_mode.get_parameters_in_mode( parameters, request.source) for beamline_parameter in parameters_in_mode: beamline_parameter.move_to_sp_rbv_no_callback() self._move_drivers()
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 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 _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 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 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)