class IPM(InOutRecordPositioner): """ Standard intensity position monitor. This is an `InOutRecordPositioner` that moves the target position to any of the four set positions, or out. Valid states are (1, 2, 3, 4, 5) or the equivalent (T1, T2, T3, T4, OUT). IPMs each also have a diode, which is implemented as the diode attribute of this class. This can easily be controlled using ``ipm.diode.insert()`` or ``ipm.diode.remove()``. """ __doc__ += basic_positioner_init state = Cmp(EpicsSignal, ':TARGET', write_pv=':TARGET:GO') diode = Cmp(InOutRecordPositioner, ":DIODE") states_list = ['T1', 'T2', 'T3', 'T4', 'OUT'] _states_alias = { 'T1': ['T1', 'TARGET1', 't1', 'target1'], 'T2': ['T2', 'TARGET2', 't2', 'target2'], 'T3': ['T3', 'TARGET3', 't3', 'target3'], 'T4': ['T4', 'TARGET4', 't4', 'target4'] } in_states = ['T1', 'T2', 'T3', 'T4'] # Assume that having any target in gives transmission 0.8 _transmission = {'T' + str(n): 0.8 for n in range(1, 5)}
class ChannelCutTower(TowerBase): """ Channel Cut tower. Components th : RotationAero Rotation stage of the channel cut crystal x : LinearAero Translation stage of the tower """ # Rotation th = Cmp(RotationAero, ":TH", desc="TH") # Translation x = Cmp(LinearAero, ":X", desc="X") def __init__(self, prefix, *args, **kwargs): super().__init__(prefix, *args, **kwargs) self._energy_motors = [self.th] @property def position(self): """ Returns the theta position of the crystal (th) in degrees. Returns ------- position : float Position of the arm in degrees. """ return self.th.position def set_energy(self, E, wait=False, check_status=True): """ Sets the angles of the crystals in the channel cut line to maximize the inputted energy. Parameters --------- E : float Energy to use for the system. wait : bool, optional Wait for motion to complete before returning the console. check_status : bool, optional Check if the motors are in a valid state to move. """ # Convert to theta theta = bragg_angle(E=E) # Check to make sure the motors are in a valid state to move if check_status: self.check_status(E) # Perform the move status = self.th.move(theta, wait=wait) return status
class MovableStand(InOutPVStatePositioner): """ Stand that can be moved. Parameters ---------- prefix: ``str`` name: ``str``, required keyword """ in_limit = Cmp(EpicsSignalRO, ':IN_DI') out_limit = Cmp(EpicsSignalRO, ':OUT_DO') _state_logic = { "in_limit": { 0: "defer", 1: "IN" }, "out_limit": { 0: "defer", 1: "OUT" } } def set(self, *args, **kwargs): raise NotImplementedError('Stand not motorized')
class PressureSwitch(PneuBase): """ Pressure switch. Components ---------- pressure : EpicsSignalRO Pressure readbac signal. """ tab_whitelist = ['bad', 'good', 'position'] pressure = Cmp(EpicsSignalRO, ":GPS") @property def position(self): """ Returns the position of the valve. Returns ------- position : str String saying the current position of the valve. Can be "OPEN" or "CLOSED". """ if self.pressure.get() == 0: return "GOOD" elif self.pressure.get() == 1: return "BAD" else: return "UNKNOWN" @property def good(self): """ Returns if the pressure is in the 'good' state. Returns ------- good : bool True if the pressure switch is in the 'good' state. """ return (self.position == "GOOD") @property def bad(self): """ Returns if the pressure is in the 'bad' state. Returns ------- bad : bool True if the pressure switch is in the 'bad' state. """ return (self.position == "BAD")
class EccController(SndDevice): """ ECC Controller """ _firm_day = Cmp(EpicsSignalRO, ":CALC:FIRMDAY") _firm_month = Cmp(EpicsSignalRO, ":CALC:FIRMMONTH") _firm_year = Cmp(EpicsSignalRO, ":CALC:FIRMYEAR") _flash = Cmp(EpicsSignal, ":RDB:FLASH", write_pv=":CMD:FLASH") @property def firmware(self): """ Returns the firmware in the same date format as the EDM screen. """ return "{0}/{1}/{2}".format(self._firm_day.value, self._firm_month.value, self._firm_year.value) @property def flash(self): """ Saves the current configuration of the controller. """ return self._flash.set(1, timeout=self.set_timeout)
class PIMPulnixDetector(PulnixDetector): """ Pulnix detector that is used in the PIM. Plugins should be added on an as needed basis here. """ image1 = Cmp(ImagePlugin, ":IMAGE1:", read_attrs=['array_data']) image2 = Cmp(ImagePlugin, ":IMAGE2:", read_attrs=['array_data']) stats2 = Cmp(StatsPlugin, ":Stats2:", read_attrs=['centroid', 'mean_value']) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.image1.stage_sigs[self.image1.nd_array_port] = 'CAM' self.image1.stage_sigs[self.image1.blocking_callbacks] = 'Yes' self.image1.stage_sigs[self.image1.enable] = 1 self.stats2.stage_sigs[self.stats2.nd_array_port] = 'CAM' self.stats2.stage_sigs[self.stats2.blocking_callbacks] = 'Yes' self.stats2.stage_sigs[self.stats2.compute_statistics] = 'Yes' self.stats2.stage_sigs[self.stats2.compute_centroid] = 'Yes' self.stats2.stage_sigs[self.stats2.centroid_threshold] = 200 self.stats2.stage_sigs[self.stats2.min_callback_time] = 0.0 self.stats2.stage_sigs[self.stats2.enable] = 1
class SeqBase(SndDevice): """ Base sequencer class. """ state_control = Cmp(EpicsSignal, ":PLYCTL") def start(self): """ Start the sequencer. """ status = self.state_control.set(1, timeout=self.timeout) status_wait(status) def stop(self): """ Stop the sequencer. """ status = self.state_control.set(0, timeout=self.timeout) status_wait(status)
class AeroBase(SndEpicsMotor): """ Base Aerotech motor class. Components power : EpicsSignal, ".CNEN" Enables or disables power to the axis. retries : EpicsSignalRO, ".RCNT" Number of retries attempted. retries_max : EpicsSignal, ".RTRY" Maximum number of retries. retries_deadband : EpicsSignal, ".RDBD" Tolerance of each retry. axis_fault : EpicsSignalRO, ":AXIS_FAULT" Fault readback for the motor. axis_status : EpicsSignalRO, ":AXIS_STATUS" Status readback for the motor. clear_error : EpicsSignal, ":CLEAR" Clear error signal. config : EpicsSignal, ":CONFIG" Signal to reconfig the motor. """ # Remove when these have been figured out low_limit_switch = Cmp(Signal) high_limit_switch = Cmp(Signal) power = Cmp(EpicsSignal, ".CNEN") retries = Cmp(EpicsSignalRO, ".RCNT") retries_max = Cmp(EpicsSignal, ".RTRY") retries_deadband = Cmp(EpicsSignal, ".RDBD") axis_status = Cmp(EpicsSignalRO, ":AXIS_STATUS") axis_fault = Cmp(EpicsSignalRO, ":AXIS_FAULT") clear_error = Cmp(EpicsSignal, ":CLEAR") config = Cmp(EpicsSignal, ":CONFIG") zero_all_proc = Cmp(EpicsSignal, ":ZERO_P.PROC") home_forward = Cmp(EpicsSignal, ".HOMF") home_reverse = Cmp(EpicsSignal, ".HOMR") dial = Cmp(EpicsSignalRO, ".DRBV") state_component = Cmp(EpicsSignal, ".SPMG") def __init__(self, prefix, name=None, *args, **kwargs): super().__init__(prefix, name=name, *args, **kwargs) self.motor_done_move.unsubscribe(self._move_changed) self.user_readback.unsubscribe(self._pos_changed) self.configuration_attrs.append("power") self._state_list = ["Stop", "Pause", "Move", "Go"] def _status_print(self, status, msg=None, ret_status=False, print_set=True, timeout=None, wait=True, reraise=False): """ Internal method that optionally returns the status object and optionally prints a message about the set. If a message is passed but print_set is False then the message is logged at the debug level. Parameters ---------- status : StatusObject or list The inputted status object. msg : str or None, optional Message to print if print_set is True. ret_status : bool, optional Return the status object of the set. print_set : bool, optional Print a short statement about the set. wait : bool, optional Wait for the status to complete. reraise : bool, optional Raise the RuntimeError in the except. Returns ------- Status Inputted status object. """ try: # Wait for the status to complete if wait: for s in as_list(status): status_wait(s, timeout) # Notify the user if msg is not None: if print_set: logger.info(msg) else: logger.debug(msg) if ret_status: return status # The operation failed for some reason except RuntimeError: error = "Operation completed, but reported an error." logger.error(error) if reraise: raise @stop_on_keyboardinterrupt def homf(self, ret_status=False, print_set=True, check_status=True): """ Home the motor forward. Parameters ---------- ret_status : bool, optional Return the status object of the set. print_set : bool, optional Print a short statement about the set. check_status : bool, optional Check if the motors are in a valid state to move. Returns ------- Status : StatusObject Status of the set. """ # Check the motor status if check_status: self.check_status() status = self.home_forward.set(1, timeout=self.set_timeout) return self._status_print(status, "Homing '{0}' forward.".format(self.desc), print_set=print_set, ret_status=ret_status) @stop_on_keyboardinterrupt def homr(self, ret_status=False, print_set=True, check_status=True): """ Home the motor in reverse. Parameters ---------- ret_status : bool, optional Return the status object of the set. print_set : bool, optional Print a short statement about the set. check_status : bool, optional Check if the motors are in a valid state to move. Returns ------- Status : StatusObject Status of the set. """ # Check the motor status if check_status: self.check_status() status = self.home_reverse.set(1, timeout=self.set_timeout) return self._status_print(status, "Homing '{0}' in reverse.".format(self.desc), print_set=print_set, ret_status=ret_status) def move(self, position, wait=False, check_status=True, timeout=None, *args, **kwargs): """ Move to a specified position, optionally waiting for motion to complete. Parameters ---------- position Position to move to. wait : bool, optional Wait for the motor to complete the motion. check_status : bool, optional Check if the motors are in a valid state to move. moved_cb : callable Call this callback when movement has finished. This callback must accept one keyword argument: 'obj' which will be set to this positioner instance. timeout : float, optional Maximum time to wait for the motion. If None, the default timeout for this positioner is used. Returns ------- status : MoveStatus Status object for the move. Raises ------ TimeoutError When motion takes longer than `timeout` ValueError On invalid positions RuntimeError If motion fails other than timing out """ # Check the motor status if check_status: self.check_status(position) logger.debug("Moving {0} to {1}".format(self.name, position)) return super().move(position, wait=wait, timeout=timeout, *args, **kwargs) def mv(self, position, wait=True, print_move=True, *args, **kwargs): """ Move to a specified position, optionally waiting for motion to complete. mv() is different from move() by catching all the common exceptions that this motor can raise and just raises a logger warning. Therefore if building higher level functionality, do not use this method and use move() instead otherwise none of these exceptions will propagate to it. Parameters ---------- position Position to move to. wait : bool, optional Wait for the motor to complete the motion. check_status : bool, optional Check if the motors are in a valid state to move. print_move : bool, optional Print a short statement about the move. Exceptions Caught ----------------- LimitError Error raised when the inputted position is beyond the soft limits. MotorDisabled Error raised if the motor is disabled and move is requested. MotorFaulted Error raised if the motor is disabled and the move is requested. MotorStopped Error raised if the motor is stopped and the move is requested. Returns ------- status : MoveStatus Status object for the move. """ try: status = super().mv(position, wait=wait, *args, **kwargs) # Notify the user that a motor has completed or the command is sent if print_move: if wait: logger.info("Move completed for '{0}'.".format(self.desc)) else: logger.info("Move command sent to '{0}'.".format( self.desc)) return status # Catch all the common motor exceptions except LimitError: logger.warning("Requested move '{0}' is outside the soft limits " "{1} for motor {2}".format(position, self.limits, self.desc)) except MotorDisabled: logger.warning( "Cannot move - motor {0} is currently disabled. Try " "running 'motor.enable()'.".format(self.desc)) except MotorFaulted: logger.warning("Cannot move - motor {0} is currently faulted. Try " "running 'motor.clear()'.".format(self.desc)) except MotorStopped: logger.warning("Cannot move - motor {0} is currently stopped. Try " "running 'motor.state=\"Go\"'.".format(self.desc)) def check_status(self, position=None): """ Checks the status of the motor to make sure it is ready to move. Checks the current position of the motor, and if a position is provided it also checks that position. Parameters ---------- position : float, optional Position to check for validity. Raises ------ MotorDisabled If the motor is disabled. MotorFaulted If the motor is faulted. MotorStopped If the motor is stopped. """ if not self.enabled: err = "Motor '{0}' is currently disabled".format(self.desc) logger.error(err) raise MotorDisabled(err) if self.faulted: err = "Motor '{0}' is currently faulted.".format(self.desc) logger.error(err) raise MotorFaulted(err) if self.state == "Stop": err = "Motor '{0}' is currently stopped.".format(self.desc) logger.error(err) raise MotorStopped(err) # Check if the current position is valid self.check_value(self.position) # Check if the move position is valid if position: self.check_value(position) def set_position(self, position_des, print_set=True): """ Sets the current position to be the inputted position by changing the offset. Parameters ---------- position_des : float The desired current position. """ # Print to console or just to the log if print_set: log_level = logger.info else: log_level = logger.debug log_level("'{0}' previous position: {0}, offset: {1}".format( self.position, self.offset)) self.offset += position_des - self.position log_level("'{0}' New position: {0}, offset: {1}".format( self.position, self.offset)) def enable(self, ret_status=False, print_set=True): """ Enables the motor power. Parameters ---------- ret_status : bool, optional Return the status object of the set. print_set : bool, optional Print a short statement about the set. Returns ------- Status The status object for setting the power signal. """ status = self.power.set(1, timeout=self.set_timeout) return self._status_print(status, "Enabled motor '{0}'.".format(self.desc), print_set=print_set, ret_status=ret_status) def disable(self, ret_status=False, print_set=True): """ Disables the motor power. Parameters ---------- ret_status : bool, optional Return the status object of the set. print_set : bool, optional Print a short statement about the set. Returns ------- Status The status object for setting the power signal. """ status = self.power.set(0, timeout=self.set_timeout) return self._status_print(status, "Disabled motor '{0}'.".format(self.desc), print_set=print_set, ret_status=ret_status) @property def enabled(self): """ Returns if the motor is curently enabled. Returns ------- enabled : bool True if the motor is powered, False if not. """ return bool(self.power.value) def clear(self, ret_status=False, print_set=True): """ Clears the motor error. Parameters ---------- ret_status : bool, optional Return the status object of the set. print_set : bool, optional Print a short statement about the set. Returns ------- Status The status object for setting the clear_error signal. """ status = self.clear_error.set(1, timeout=self.set_timeout) return self._status_print(status, "Cleared motor '{0}'.".format(self.desc), print_set=print_set, ret_status=ret_status) def reconfig(self, ret_status=False, print_set=True): """ Re-configures the motor. Parameters ---------- ret_status : bool, optional Return the status object of the set. print_set : bool, optional Print a short statement about the set. Returns ------- Status The status object for setting the config signal. """ status = self.config.set(1, timeout=self.set_timeout) return self._status_print(status, "Reconfigured motor '{0}'.".format( self.desc), print_set=print_set, ret_status=ret_status) @property def faulted(self): """ Returns the current fault with the motor. Returns ------- faulted Fault enumeration. """ if self.axis_fault.connected: return bool(self.axis_fault.value) else: return None def zero_all(self, ret_status=False, print_set=True): """ Sets the current position to be the zero position of the motor. Parameters ---------- ret_status : bool, optional Return the status object of the set. print_set : bool, optional Print a short statement about the set. Returns ------- status : StatusObject Status object for the set. """ status = self.zero_all_proc.set(1, timeout=self.set_timeout) return self._status_print(status, "Zeroed motor '{0}'.".format(self.desc), print_set=print_set, ret_status=ret_status) @property def state(self): """ Returns the state of the motor. State can be one of the following: 'Stop', 'Pause', 'Move', 'Go' Returns ------- state : str The current state of the motor """ return self._state_list[self.state_component.get()] @state.setter def state(self, val, ret_status=False, print_set=True): """ Sets the state of the motor. Inputted state can be one of the following states or the index of the desired state: 'Stop', 'Pause', 'Move', 'Go' Alias for set_state((val, False, True) Parameters ---------- val : int or str ret_status : bool, optional Return the status object of the set. print_set : bool, optional Print a short statement about the set. Returns ------- Status The status object for setting the state signal. """ try: return self.set_state(val, ret_status, print_set) except ValueError: logger.info("State must be one of the following: {0}".format( self._state_list)) def set_state(self, state, ret_status=True, print_set=False): """ Sets the state of the motor. Inputted state can be one of the following states or the index of the desired state: 'Stop', 'Pause', 'Move', 'Go' Parameters ---------- val : int or str ret_status : bool, optional Return the status object of the set. print_set : bool, optional Print a short statement about the set. Returns ------- Status The status object for setting the state signal. """ # Make sure it is in title case if it's a string val = state if isinstance(state, str): val = state.title() # Make sure it is a valid state or enum if val not in self._state_list + list(range(len(self._state_list))): error = "Invalid state inputted: '{0}'.".format(val) logger.error(error) raise ValueError(error) # Lets enforce it's a string or value status = self.state_component.set(val, timeout=self.set_timeout) return self._status_print(status, "Changed state of '{0} to '{1}'.".format( self.desc, val), print_set=print_set, ret_status=ret_status) def ready_motor(self, ret_status=False, print_set=True): """ Sets the motor to the ready state by clearing any errors, enabling it, and setting the state to be 'Go'. Parameters ---------- ret_status : bool, optional Return the status object of the set. print_set : bool, optional Print a short statement about the set. Returns ------- Status The status object for all the sets. """ status = [self.clear(ret_status=True, print_set=False)] status.append(self.enable(ret_status=True, print_set=False)) status.append(self.set_state("Go", ret_status=True, print_set=False)) return self._status_print(status, "Motor '{0}' is now ready to move.".format( self.desc), print_set=print_set, ret_status=ret_status) def expert_screen(self, print_msg=True): """ Launches the expert screen for the motor. Parameters ---------- print_msg : bool, optional Prints that the screen is being launched. """ path = absolute_submodule_path( "hxrsnd/screens/motor_expert_screens.sh") if print_msg: logger.info("Launching expert screen.") os.system("{0} {1} {2} &".format(path, self.prefix, "aerotech")) def status(self, status="", offset=0, print_status=True, newline=False, short=False): """ Returns the status of the device. Parameters ---------- status : str, optional The string to append the status to. offset : int, optional Amount to offset each line of the status. print_status : bool, optional Determines whether the string is printed or returned. newline : bool, optional Adds a new line to the end of the string. Returns ------- status : str Status string. """ if short: status += "\n{0}{1:<16}|{2:^16.3f}|{3:^16.3f}".format( " " * offset, self.desc, self.position, self.dial.value) else: status += "{0}{1}\n".format(" " * offset, self.desc) status += "{0}PV: {1:>25}\n".format(" " * (offset + 2), self.prefix) status += "{0}Enabled: {1:>20}\n".format(" " * (offset + 2), str(self.enabled)) status += "{0}Faulted: {1:>20}\n".format(" " * (offset + 2), str(self.faulted)) status += "{0}State: {1:>22}\n".format(" " * (offset + 2), str(self.state)) status += "{0}Position: {1:>19}\n".format(" " * (offset + 2), np.round(self.wm(), 6)) status += "{0}Dial: {1:>23}\n".format(" " * (offset + 2), np.round(self.dial.value, 6)) status += "{0}Limits: {1:>21}\n".format( " " * (offset + 2), str((int(self.low_limit), int(self.high_limit)))) if newline: status += "\n" if print_status is True: logger.info(status) else: return status
class EccBase(SndMotor, PositionerBase): """ ECC Motor Class """ # position user_readback = Cmp(EpicsSignalRO, ":POSITION", auto_monitor=True) user_setpoint = Cmp(EpicsSignal, ":CMD:TARGET") # limits upper_ctrl_limit = Cmp(EpicsSignal, ':CMD:TARGET.HOPR') lower_ctrl_limit = Cmp(EpicsSignal, ':CMD:TARGET.LOPR') # configuration motor_egu = Cmp(EpicsSignalRO, ":UNIT") motor_amplitude = Cmp(EpicsSignal, ":CMD:AMPL") motor_dc = Cmp(EpicsSignal, ":CMD:DC") motor_frequency = Cmp(EpicsSignal, ":CMD:FREQ") # motor status motor_connected = Cmp(EpicsSignalRO, ":ST_CONNECT") motor_enabled = Cmp(EpicsSignalRO, ":ST_ENABLED") motor_referenced = Cmp(EpicsSignalRO, ":ST_REFVAL") motor_error = Cmp(EpicsSignalRO, ":ST_ERROR") motor_is_moving = Cmp(EpicsSignalRO, ":RD_MOVING") motor_done_move = Cmp(EpicsSignalRO, ":RD_INRANGE") high_limit_switch = Cmp(EpicsSignal, ":ST_EOT_FWD") low_limit_switch = Cmp(EpicsSignal, ":ST_EOT_BWD") motor_reference_position = Cmp(EpicsSignalRO, ":REF_POSITION") # commands motor_stop = Cmp(EpicsSignal, ":CMD:STOP") motor_reset = Cmp(EpicsSignal, ":CMD:RESET.PROC") motor_enable = Cmp(EpicsSignal, ":CMD:ENABLE") @property def position(self): """ Returns the current position of the motor. Returns ------- position : float Current position of the motor. """ return self.user_readback.value @property def reference(self): """ Returns the reference position of the motor. Returns ------- position : float Reference position of the motor. """ return self.motor_reference_position.value @property def egu(self): """ The engineering units (EGU) for a position Returns ------- Units : str Engineering units for the position. """ return self.motor_egu.get() def _status_print(self, status, msg=None, ret_status=False, print_set=True, timeout=None, wait=True, reraise=False): """ Internal method that optionally returns the status object and optionally prints a message about the set. If a message is passed but print_set is False then the message is logged at the debug level. Parameters ---------- status : StatusObject or list The inputted status object. msg : str or None, optional Message to print if print_set is True. ret_status : bool, optional Return the status object of the set. print_set : bool, optional Print a short statement about the set. wait : bool, optional Wait for the status to complete. reraise : bool, optional Raise the RuntimeError in the except. Returns ------- Status Inputted status object. """ try: # Wait for the status to complete if wait: for s in as_list(status): status_wait(s, timeout) # Notify the user if msg is not None: if print_set: logger.info(msg) else: logger.debug(msg) if ret_status: return status # The operation failed for some reason except RuntimeError: error = "Operation completed, but reported an error." logger.error(error) if reraise: raise def enable(self, ret_status=False, print_set=True): """ Enables the motor power. Parameters ---------- ret_status : bool, optional Return the status object of the set. print_set : bool, optional Print a short statement about the set. Returns ------- Status The status object for setting the power signal. """ status = self.motor_enable.set(1, timeout=self.set_timeout) return self._status_print(status, "Enabled motor '{0}'".format(self.desc), ret_status=ret_status, print_set=print_set) def disable(self, ret_status=False, print_set=True): """ Disables the motor power. Parameters ---------- ret_status : bool, optional Return the status object of the set. print_set : bool, optional Print a short statement about the set. Returns ------- Status The status object for setting the power signal. """ status = self.motor_enable.set(0, timeout=self.set_timeout) return self._status_print(status, "Disabled motor '{0}'".format(self.desc), ret_status=ret_status, print_set=print_set) @property def enabled(self): """ Returns if the motor is curently enabled. Returns ------- enabled : bool True if the motor is powered, False if not. """ return bool(self.motor_enable.value) @property def connected(self): """ Returns if the motor is curently connected. Returns ------- connected : bool True if the motor is connected, False if not. """ return bool(self.motor_connected.value) @property def referenced(self): """ Returns if the motor is curently referenced. Returns ------- referenced : bool True if the motor is referenced, False if not. """ return bool(self.motor_referenced.value) @property def error(self): """ Returns the current error with the motor. Returns ------- error : bool Error enumeration. """ return bool(self.motor_error.value) def reset(self, ret_status=False, print_set=True): """ Sets the current position to be the zero position of the motor. Returns ------- status : StatusObject Status object for the set. """ status = self.motor_reset.set(1, timeout=self.set_timeout) return self._status_print(status, "Reset motor '{0}'".format(self.desc), ret_status=ret_status, print_set=print_set) def move(self, position, check_status=True, timeout=None, *args, **kwargs): """ Move to a specified position. Parameters ---------- position Position to move to check_status : bool, optional Check if the motors are in a valid state to move. Returns ------- status : MoveStatus Status object for the move. Raises ------ TimeoutError When motion takes longer than `timeout` ValueError On invalid positions RuntimeError If motion fails other than timing out """ # Check the motor status if check_status: self.check_status(position) logger.debug("Moving {0} to {1}".format(self.name, position)) # Begin the move process return self.user_setpoint.set(position, timeout=timeout) def mv(self, position, print_move=True, *args, **kwargs): """ Move to a specified position, optionally waiting for motion to complete. mv() is different from move() by catching all the common exceptions that this motor can raise and just raises a logger warning. Therefore if building higher level functionality, do not use this method and use move() instead otherwise none of these exceptions will propagate to it. Parameters ---------- position Position to move to. print_move : bool, optional Print a short statement about the move. Exceptions Caught ----------------- LimitError Error raised when the inputted position is beyond the soft limits. MotorDisabled Error raised if the motor is disabled and move is requested. MotorFaulted Error raised if the motor is disabled and the move is requested. Returns ------- status : MoveStatus Status object for the move. """ try: status = super().mv(position, *args, **kwargs) # Notify the user that a motor has completed or the command is sent if print_move: logger.info("Move command sent to '{0}'.".format(self.desc)) # Check if a status object is desired return status # Catch all the common motor exceptions except LimitError: logger.warning("Requested move '{0}' is outside the soft limits " "{1} for motor {2}".format(position, self.limits, self.desc)) except MotorDisabled: logger.warning( "Cannot move - motor {0} is currently disabled. Try " "running 'motor.enable()'.".format(self.desc)) except MotorFaulted: logger.warning("Cannot move - motor {0} is currently faulted. Try " "running 'motor.clear()'.".format(self.desc)) def check_status(self, position=None): """ Checks the status of the motor to make sure it is ready to move. Checks the current position of the motor, and if a position is provided it also checks that position. Parameters ---------- position : float Position to check for validity. Raises ------ MotorDisabled If the motor is disabled. MotorError If the motor has an error. """ if not self.enabled: err = "Motor '{0}' is currently disabled.".format(self.desc) logger.error(err) raise MotorDisabled(err) if self.error: err = "Motor '{0}' currently has an error.".format(self.desc) logger.error(err) raise MotorError(err) # Check if the current position is valid self.check_value(self.position) # Check if the move position is valid if position: self.check_value(position) def check_value(self, position): """ Checks to make sure the inputted value is valid. Parameters ---------- position : float Position to check for validity Raises ------ ValueError If position is None, NaN or Inf LimitError If the position is outside the soft limits. """ # Check for invalid positions if position is None or np.isnan(position) or np.isinf(position): raise ValueError("Invalid value inputted: '{0}'".format(position)) # Check if it is within the soft limits if not (self.low_limit <= position <= self.high_limit): err_str = "Requested value {0} outside of range: [{1}, {2}]" logger.warn( err_str.format(position, self.low_limit, self.high_limit)) raise LimitError(err_str) def stop(self, success=False, ret_status=False, print_set=True): """ Stops the motor. Parameters ---------- ret_status : bool, optional Return the status object of the set. print_set : bool, optional Print a short statement about the set. Returns ------- Status : StatusObject Status of the set. """ status = self.motor_stop.set(1, wait=False, timeout=self.set_timeout) super().stop(success=success) return self._status_print(status, "Stopped motor '{0}'".format(self.desc), ret_status=ret_status, print_set=print_set) def expert_screen(self, print_msg=True): """ Launches the expert screen for the motor. Parameters ---------- print_msg : bool, optional Prints that the screen is being launched. """ # Get the absolute path to the screen path = absolute_submodule_path( "hxrsnd/screens/motor_expert_screens.sh") if print_msg: logger.info("Launching expert screen.") os.system("{0} {1} {2} &".format(path, self.prefix, "attocube")) def set_limits(self, llm, hlm): """ Sets the limits of the motor. Alias for limits = (llm, hlm). Parameters ---------- llm : float Desired low limit. hlm : float Desired low limit. """ self.limits = (llm, hlm) @property def high_limit(self): """ Returns the upper limit fot the user setpoint. Returns ------- high_limit : float """ return self.upper_ctrl_limit.value @high_limit.setter def high_limit(self, value): """ Sets the high limit for user setpoint. """ self.upper_ctrl_limit.put(value) @property def low_limit(self): """ Returns the lower limit fot the user setpoint. Returns ------- low_limit : float """ return self.lower_ctrl_limit.value @low_limit.setter def low_limit(self, value): """ Sets the high limit for user setpoint. """ self.lower_ctrl_limit.put(value) @property def limits(self): """ Returns the limits of the motor. Returns ------- limits : tuple """ return (self.low_limit, self.high_limit) @limits.setter def limits(self, value): """ Sets the limits for user setpoint. """ self.low_limit = value[0] self.high_limit = value[1] def status(self, status="", offset=0, print_status=True, newline=False, short=False): """ Returns the status of the device. Parameters ---------- status : str, optional The string to append the status to. offset : int, optional Amount to offset each line of the status. print_status : bool, optional Determines whether the string is printed or returned. newline : bool, optional Adds a new line to the end of the string. Returns ------- status : str Status string. """ if short: status += "\n{0}{1:<16}|{2:^16.3f}|{3:^16.3f}".format( " " * offset, self.desc, self.position, self.reference) else: status += "{0}{1}\n".format(" " * offset, self.desc) status += "{0}PV: {1:>25}\n".format(" " * (offset + 2), self.prefix) status += "{0}Enabled: {1:>20}\n".format(" " * (offset + 2), str(self.enabled)) status += "{0}Faulted: {1:>20}\n".format(" " * (offset + 2), str(self.error)) status += "{0}Position: {1:>19}\n".format(" " * (offset + 2), np.round(self.wm(), 6)) status += "{0}Limits: {1:>21}\n".format( " " * (offset + 2), str((int(self.low_limit), int(self.high_limit)))) if newline: status += "\n" if print_status is True: logger.info(status) else: return status
class LODCM(InOutRecordPositioner): """ Large Offset Dual Crystal Monochromator This is the device that allows XPP and XCS to multiplex with downstream hutches. It contains two crystals that steer/split the beam and a number of diagnostic devices between them. Beam can continue onto the main line, onto the mono line, onto both, or onto neither. This positioner only considers the h1n and diagnostic motors. %s main_line: ``str``, optional Name of the main, no-bounce beamline. mono_line: ``str``, optional Name of the mono, double-bounce beamline. """ __doc__ = __doc__ % basic_positioner_init state = Cmp(EpicsSignal, ':H1N', write_pv=':H1N:GO') yag = Cmp(YagLom, ":DV") dectris = Cmp(Dectris, ":DH") diode = Cmp(InOutRecordPositioner, ":DIODE") foil = Cmp(Foil, ":FOIL") states_list = ['OUT', 'C', 'Si'] in_states = ['C', 'Si'] # TBH these are guessed. Please replace if you know better. These don't # need to be 100% accurate, but they should reflect a reasonable reduction # in transmission. _transmission = {'C': 0.8, 'Si': 0.7} def __init__(self, prefix, *, name, main_line='MAIN', mono_line='MONO', **kwargs): super().__init__(prefix, name=name, **kwargs) self.main_line = main_line self.mono_line = mono_line @property def branches(self): """ Returns ------- branches: ``list`` of ``str`` A list of possible destinations. """ return [self.main_line, self.mono_line] @property def destination(self): """ Which beamline the light is reaching. Indeterminate states will show as blocked. Returns ------- destination: ``list`` of ``str`` ``self.main_line`` if the light continues on the main line. ``self.mono_line`` if the light continues on the mono line. """ if self.position == 'OUT': dest = [self.main_line] elif self.position == 'Si': dest = [self.mono_line] elif self.position == 'C': dest = [self.main_line, self.mono_line] else: dest = [] if not self._dia_clear and self.mono_line in dest: dest.remove(self.mono_line) return dest @property def _dia_clear(self): """ Check if the diagnostics are clear. All but the diode may prevent beam. Returns ------- diag_clear: ``bool`` ``False`` if the diagnostics will prevent beam. """ yag_clear = self.yag.removed dectris_clear = self.dectris.removed foil_clear = self.foil.removed return all((yag_clear, dectris_clear, foil_clear)) def remove_dia(self, moved_cb=None, timeout=None, wait=False): """ Remove all diagnostic components. """ logger.debug('Removing %s diagnostics', self.name) status = NullStatus() for dia in (self.yag, self.dectris, self.diode, self.foil): status = status & dia.remove(timeout=timeout, wait=False) if moved_cb is not None: status.add_callback(functools.partial(moved_cb, obj=self)) if wait: status_wait(status) return status remove_dia.__doc__ += insert_remove
class NotepadScanStatus(Device): istep = Cmp(EpicsSignal, ":ISTEP") isscan = Cmp(EpicsSignal, ":ISSCAN") nshots = Cmp(EpicsSignal, ":NSHOTS") nsteps = Cmp(EpicsSignal, ":NSTEPS") var0 = Cmp(EpicsSignal, ":SCANVAR00") var1 = Cmp(EpicsSignal, ":SCANVAR01") var2 = Cmp(EpicsSignal, ":SCANVAR02") var0_max = Cmp(EpicsSignal, ":MAX00") var1_max = Cmp(EpicsSignal, ":MAX01") var2_max = Cmp(EpicsSignal, ":MAX02") var0_min = Cmp(EpicsSignal, ":MIN00") var1_min = Cmp(EpicsSignal, ":MIN01") var2_min = Cmp(EpicsSignal, ":MIN02") def clean_fields(self): for sig_name in self.signal_names: sig = getattr(self, sig_name) val = sig.value if isinstance(val, (int, float)): sig.put(0) elif isinstance(val, str): sig.put('')
class DelayTower(TowerBase): """ Delay Tower Components tth : RotationAeroInterlocked Rotation axis of the entire delay arm. th1 : RotationAero Rotation axis of the static crystal. th2 : RotationAero Rotation axis of the delay crystal. x : LinearAero Linear stage for insertion/bypass of the tower. L : LinearAero Linear stage for the delay crystal. y1 : TranslationEcc Y translation for the static crystal. y2 : TranslationEcc Y translation for the delay crystal. chi1 : GoniometerEcc Goniometer on static crystal. chi2 : GoniometerEcc Goniometer on delay crystal. dh : DiodeEcc Diode insertion motor. diode : HamamatsuDiode Diode between the static and delay crystals. temp : OmegaRTD RTD temperature sensor for the nitrogen. """ # Rotation stages tth = Cmp(InterRotationAero, ":TTH", desc="TTH") th1 = Cmp(RotationAero, ":TH1", desc="TH1") th2 = Cmp(RotationAero, ":TH2", desc="TH2") # Linear stages x = Cmp(InterLinearAero, ":X", desc="X") L = Cmp(InterLinearAero, ":L", desc="L") # # Y Crystal motion y1 = Cmp(TranslationEcc, ":Y1", desc="Y1") y2 = Cmp(TranslationEcc, ":Y2", desc="Y2") # Chi motion chi1 = Cmp(GoniometerEcc, ":CHI1", desc="CHI1") chi2 = Cmp(GoniometerEcc, ":CHI2", desc="CHI2") # Diode motion dh = Cmp(DiodeEcc, ":DH", desc="DH") # # Diode # diode = Cmp(HamamatsuDiode, ":DIODE", desc="Tower Diode") # # Temperature monitor # temp = Cmp(OmegaRTD, ":TEMP", desc="Tower RTD") def __init__(self, prefix, *args, **kwargs): super().__init__(prefix, *args, **kwargs) self._energy_motors = [self.tth, self.th1, self.th2] @property def position(self): """ Returns the theta position of the arm (tth) in degrees. Returns ------- position : float Position of the arm in degrees. """ return self.tth.position def _get_move_positions(self, E): """ Returns the list of positions that each of the energy motors need to move to based on the inputted theta. tth moves to 2*theta while all the other motors move to theta. Parameters ---------- E : float Energy to compute the motor move positions for. Returns ------- positions : list List of positions each of the energy motors need to move to. """ # Convert to theta theta = bragg_angle(E=E) # Get the positions positions = [] for motor in self._energy_motors: if motor is self.tth: positions.append(2 * theta) else: positions.append(theta) return positions def set_energy(self, E, wait=False, check_status=True): """ Sets the angles of the crystals in the delay line to maximize the inputted energy. Parameters --------- E : float Energy to use for the system. wait : bool, optional Wait for each motor to complete the motion. check_status : bool, optional Check if the motors are in a valid state to move. """ # Check to make sure the motors are in a valid state to move if check_status: self.check_status(energy=E) # Perform the move status = [ motor.move(pos, wait=False, check_status=False) for motor, pos in zip(self._energy_motors, self._get_move_positions(E)) ] # Wait for the motions to finish if wait: for s in status: logger.info("Waiting for {} to finish move ...".format( s.device.name)) status_wait(s) return status def set_length(self, position, wait=False, *args, **kwargs): """ Sets the position of the linear delay stage in mm. Parameters ---------- position : float Position to move the delay motor to. wait : bool, optional Wait for motion to complete before returning the console. Returns ------- status : MoveStatus Status object of the move. """ return self.L.move(position, wait=wait, *args, **kwargs) @property def length(self): """ Returns the position of the linear delay stage (L) in mm. Returns ------- position : float Position in mm of the linear delay stage. """ return self.L.position @length.setter def length(self, position): """ Sets the position of the linear delay stage in mm. Parameters ---------- position : float Position to move the delay motor to. """ status = self.L.mv(position) @property def theta(self): """ Bragg angle the tower is currently set to maximize. Returns ------- position : float Current position of the tower. """ return self.position / 2
class SplitAndDelay(SndDevice): """ Hard X-Ray Split and Delay System. Components t1 : DelayTower Tower 1 in the split and delay system. t4 : DelayTower Tower 4 in the split and delay system. t2 : ChannelCutTower Tower 2 in the split and delay system. t3 : ChannelCutTower Tower 3 in the split and delay system. ab : SndPneumatics Vacuum device object for the system. di : HamamatsuXYMotionCamDiode Input diode for the system. dd : HamamatsuXYMotionCamDiode Diode between the two delay towers. do : HamamatsuXYMotionCamDiode Output diode for the system. dci : HamamatsuXMotionDiode Input diode for the channel cut line. dcc : HamamatsuXMotionDiode Diode between the two channel cut towers. dco : HamamatsuXMotionDiode Input diode for the channel cut line. E1 : Energy1Macro Delay energy pseudomotor. E2 : Energy2Macro Channel cut energy pseudomotor. delay : DelayMacro Delay pseudomotor. """ tab_component_names = True tab_whitelist = [ 'st', 'status', 'diag_status', 'theta1', 'theta2', 'main_screen', 'status' ] # Delay Towers t1 = Cmp(DelayTower, ":T1", pos_inserted=21.1, pos_removed=0, desc="Tower 1") t4 = Cmp(DelayTower, ":T4", pos_inserted=21.1, pos_removed=0, desc="Tower 4") # Channel Cut Towers t2 = Cmp(ChannelCutTower, ":T2", pos_inserted=None, pos_removed=0, desc="Tower 2") t3 = Cmp(ChannelCutTower, ":T3", pos_inserted=None, pos_removed=0, desc="Tower 3") # Pneumatic Air Bearings ab = Cmp(SndPneumatics, "") # SnD and Delay line diagnostics di = Cmp(HamamatsuXMotionDiode, ":DIA:DI", desc="DI") dd = Cmp(HamamatsuXYMotionCamDiode, ":DIA:DD", desc="DD") do = Cmp(HamamatsuXMotionDiode, ":DIA:DO", desc="DO") # Channel Cut Diagnostics dci = Cmp(HamamatsuXMotionDiode, ":DIA:DCI", block_pos=-5, desc="DCI") dcc = Cmp(HamamatsuXYMotionCamDiode, ":DIA:DCC", block_pos=-5, desc="DCC") dco = Cmp(HamamatsuXMotionDiode, ":DIA:DCO", block_pos=-5, desc="DCO") # Macro motors E1 = Cmp(Energy1Macro, "", desc="Delay Energy") E1_cc = Cmp(Energy1CCMacro, "", desc="CC Delay Energy") E2 = Cmp(Energy2Macro, "", desc="CC Energy") delay = Cmp(DelayMacro, "", desc="Delay") def __init__(self, prefix, name=None, daq=None, RE=None, *args, **kwargs): super().__init__(prefix, name=name, *args, **kwargs) self.daq = daq self.RE = RE self._delay_towers = [self.t1, self.t4] self._channelcut_towers = [self.t2, self.t3] self._towers = self._delay_towers + self._channelcut_towers self._delay_diagnostics = [self.di, self.dd, self.do] self._channelcut_diagnostics = [self.dci, self.dcc, self.dco] self._diagnostics = self._delay_diagnostics + self._channelcut_diagnostics # Set the position calculators of dd and dcc self.dd.pos_func = lambda: \ self.E1._get_delay_diagnostic_position() self.dcc.pos_func = lambda: \ self.E2._get_channelcut_diagnostic_position() def diag_status(self): """ Prints a string containing the blocking status and the position of the motor. """ status = "\n{0}{1:<14}|{2:^16}|{3:^16}\n{4}{5}".format( " " * 2, "Diagnostic", "Blocking", "Position", " " * 2, "-" * 50) for diag in self._diagnostics: status += "\n{0}{1:<14}|{2:^16}|{3:^16.3f}".format( " " * 2, diag.desc, str(diag.blocked), diag.x.position) logger.info(status) @property def theta1(self): """ Returns the bragg angle the the delay line is currently set to maximize. Returns ------- theta1 : float The bragg angle the delay line is currently set to maximize in degrees. """ return self.t1.theta @property def theta2(self): """ Returns the bragg angle the the delay line is currently set to maximize. Returns ------- theta2 : float The bragg angle the channel cut line is currently set to maximize in degrees. """ return self.t2.theta def main_screen(self, print_msg=True): """ Launches the main SnD screen. """ # Get the absolute path to the screen path = absolute_submodule_path("hxrsnd/screens/snd_main") if print_msg: logger.info("Launching expert screen.") logger.error('TODO - not yet implemented (path: %s)', path) # NOTE: this was the command ,where `p` and `axis` were not defined: # os.system("{0} {1} {2} &".format(path, p, axis)) def status(self, print_status=True): """ Returns the status of the split and delay system. Returns ------- Status : str """ status = "Split and Delay System Status\n" status += "-----------------------------" status = self.E1.status(status, 0, print_status=False) status = self.E2.status(status, 0, print_status=False) status = self.delay.status(status, 0, print_status=False, newline=True) status = self.t1.status(status, 0, print_status=False, newline=True) status = self.t2.status(status, 0, print_status=False, newline=True) status = self.t3.status(status, 0, print_status=False, newline=True) status = self.t4.status(status, 0, print_status=False, newline=True) status = self.ab.status(status, 0, print_status=False, newline=False) if print_status: logger.info(status) else: logger.debug(status) return status
class ProportionalValve(PneuBase): """ Class for the proportional pneumatic valves. Components ---------- valve : EpicsSignal Valve control and readback pv. """ tab_whitelist = ['close', 'closed', 'open', 'opened', 'position'] valve = Cmp(EpicsSignal, ":VGP") def open(self): """ Closes the valve. """ if self.opened: logger.info("Valve currently open.") else: return self.valve.set(1, timeout=self.set_timeout) def close(self): """ Closes the valve. """ if self.closed: logger.info("Valve currently closed.") else: return self.valve.set(0, timeout=self.set_timeout) @property def position(self): """ Returns the position of the valve. Returns ------- position : str String saying the current position of the valve. Can be "OPEN" or "CLOSED". """ if self.valve.get() == 1: return "OPEN" elif self.valve.get() == 0: return "CLOSED" else: return "UNKNOWN" @property def opened(self): """ Returns if the valve is in the opened state. Returns ------- opened : bool True if the valve is currently in the 'opened' position. """ return (self.position == "OPEN") @property def closed(self): """ Returns if the valve is in the closed state. Returns ------- closed : bool True if the valve is currently in the 'closed' position. """ return (self.position == "CLOSED")
class SndPneumatics(SndDevice): """ Class that contains the various pneumatic components of the system. Components ---------- t1_valve : ProportionalValve Proportional valve on T1. t4_valve : ProportionalValve Proportional valve on T4. vac_valve : ProportionalValve Proportional valve on the overall system. t1_pressure : PressureSwitch Pressure switch on T1. t4_pressure : PressureSwitch Pressure switch on T4. vac_pressure : PressureSwitch Pressure switch on the overall system. """ tab_whitelist = ['close', 'open', 'pressures', 'status', 'valves'] t1_valve = Cmp(ProportionalValve, ":N2:T1", desc="T1 Valve") t4_valve = Cmp(ProportionalValve, ":N2:T4", desc="T4 Valve") vac_valve = Cmp(ProportionalValve, ":VAC", desc="Vacuum Valve") t1_pressure = Cmp(PressureSwitch, ":N2:T1", desc="T1 Pressure") t4_pressure = Cmp(PressureSwitch, ":N2:T4", desc="T4 Pressure") vac_pressure = Cmp(PressureSwitch, ":VAC", desc="Vacuum Pressure") def __init__(self, prefix, name=None, *args, **kwargs): super().__init__(prefix, name=name, *args, **kwargs) self._valves = [self.t1_valve, self.t4_valve, self.vac_valve] self._pressure_switches = [ self.t1_pressure, self.t4_pressure, self.vac_pressure ] def status(self, status="", offset=0, print_status=True, newline=False): """ Returns the status of the vacuum system. Parameters ---------- status : str, optional The string to append the status to. offset : int, optional Amount to offset each line of the status. print_status : bool, optional Determines whether the string is printed or returned. newline : bool, optional Adds a new line to the end of the string. Returns ------- status : str Status string. """ status += "\n{0}Pneumatics".format(" " * offset) status += "\n{0}{1}\n{0}{2:^16}|{3:^16}\n{0}{4}\n".format( " " * (offset + 2), "-" * 34, "Device", "State", "-" * 34) for valve in self._valves: status += valve.status(offset=offset + 2, print_status=False) for pressure in self._pressure_switches: status += pressure.status(offset=offset + 2, print_status=False) if newline: status += "\n" if print_status is True: logger.info(status) else: return status def open(self): """ Opens all the valves in the vacuum system. """ logging.info("Opening valves in SnD system.") for valve in self._valves: valve.open() def close(self): """ Opens all the valves in the vacuum system. """ logging.info("Closing valves in SnD system.") for valve in self._valves: valve.close() @property def valves(self): """ Prints the positions of all the valves in the system. """ status = "" for valve in self._valves: status += valve.status(print_status=False) logger.info(status) @property def pressures(self): """ Prints the pressures of all the pressure switches in the system. """ status = "" for pressure in self._pressure_switches: status += pressure.status(print_status=False) logger.info(status) def __repr__(self): """ Returns the status of the device. Alias for status(). Returns ------- status : str Status string. """ return self.status(print_status=False)