Source code for epicsMotor

"""
This module provides support for the EPICS motor record.

Author:         Mark Rivers

Created:        Sept. 16, 2002

Modifications:

- Mar. 7, 2017 Xiaoqiang Wang

  - Reformat the docstring and code indent.
  - Use class property to expose certain fields.
"""
import time

import epicsPV


[docs]class epicsMotor(object): """ This module provides a class library for the EPICS motor record. It uses the :class:`epicsPV.epicsPV` class, which is in turn a subclass of :class:`CaChannel.CaChannel` Certain motor record fields are exposed as class properties. They can be both read and written unless otherwise noted. =============== =========================== ===== Property Description Field =============== =========================== ===== slew_speed Slew speed or velocity .VELO base_speed Base or starting speed .VBAS acceleration Acceleration time (sec) .ACCL description Description of motor .DESC resolution Resolution (units/step) .MRES high_limit High soft limit (user) .HLM low_limit Low soft limit (user) .LLM dial_high_limit High soft limit (dial) .DHLM dial_low_limit Low soft limit (dial) .DLLM backlash Backlash distance .BDST offset Offset from dial to user .OFF done_moving 1=Done, 0=Moving, read-only .DMOV =============== =========================== ===== >>> m = epicsMotor('13BMD:m38') >>> m.move(10) # Move to position 10 in user coordinates >>> m.wait() # Wait for motor to stop moving >>> m.move(50, dial=True) # Move to position 50 in dial coordinates >>> m.wait() # Wait for motor to stop moving >>> m.move(1, step=True, relative=True) # Move 1 step relative to current position >>> m.wait(start=True, stop=True) # Wait for motor to start, then to stop >>> m.stop() # Stop moving immediately >>> high = m.high_limit # Get the high soft limit in user coordinates >>> m.dial_high_limit = 100 # Set the high limit to 100 in dial coodinates >>> speed = m.slew_speed # Get the slew speed >>> m.acceleration = 0.1 # Set the acceleration to 0.1 seconds >>> val = m.get_position() # Get the desired motor position in user coordinates >>> dval = m.get_position(dial=True)# Get the desired motor position in dial coordinates >>> rbv = m.get_position(readback=True) # Get the actual position in user coordinates >>> rrbv = m.get_position(readback=True, step=True) # Get the actual motor position in steps >>> m.set_position(100) # Set the current position to 100 in user coordinates >>> m.set_position(10000, step=True) # Set the current position to 10000 steps """ class PVProperty(object): def __init__(self, name, readonly=False): self.name = name self.readonly = readonly def __get__(self, instance, owner): if instance is None: return self return instance.pvs[self.name].getw() def __set__(self, instance, value): if instance is None or self.readonly: return instance.pvs[self.name].putw(value) slew_speed = PVProperty('velo') base_speed = PVProperty('vbas') acceleration = PVProperty('accl') description = PVProperty('desc') resolution = PVProperty('mres') high_limit = PVProperty('hlm') low_limit = PVProperty('llm') dial_high_limit = PVProperty('dhlm') dial_low_limit = PVProperty('dllm') backlash = PVProperty('bdst') offset = PVProperty('off') done_moving = PVProperty('dmov', readonly=True)
[docs] def __init__(self, name): """ Creates a new epicsMotor instance. :param str name: The name of the EPICS motor record without any trailing period or field name. >>> m = epicsMotor('13BMD:m38') """ self.pvs = {'val': epicsPV.epicsPV(name+'.VAL', wait=False), 'dval': epicsPV.epicsPV(name+'.DVAL', wait=False), 'rval': epicsPV.epicsPV(name+'.RVAL', wait=False), 'rlv': epicsPV.epicsPV(name+'.RLV', wait=False), 'rbv': epicsPV.epicsPV(name+'.RBV', wait=False), 'drbv': epicsPV.epicsPV(name+'.DRBV', wait=False), 'rrbv': epicsPV.epicsPV(name+'.RRBV', wait=False), 'dmov': epicsPV.epicsPV(name+'.DMOV', wait=False), 'stop': epicsPV.epicsPV(name+'.STOP', wait=False), 'velo': epicsPV.epicsPV(name+'.VELO', wait=False), 'vbas': epicsPV.epicsPV(name+'.VBAS', wait=False), 'accl': epicsPV.epicsPV(name+'.ACCL', wait=False), 'desc': epicsPV.epicsPV(name+'.DESC', wait=False), 'mres': epicsPV.epicsPV(name+'.MRES', wait=False), 'hlm': epicsPV.epicsPV(name+'.HLM', wait=False), 'llm': epicsPV.epicsPV(name+'.LLM', wait=False), 'dhlm': epicsPV.epicsPV(name+'.DHLM', wait=False), 'dllm': epicsPV.epicsPV(name+'.DLLM', wait=False), 'bdst': epicsPV.epicsPV(name+'.BDST', wait=False), 'set': epicsPV.epicsPV(name+'.SET', wait=False), 'lvio': epicsPV.epicsPV(name+'.LVIO', wait=False), 'lls': epicsPV.epicsPV(name+'.LLS', wait=False), 'hls': epicsPV.epicsPV(name+'.HLS', wait=False), 'off': epicsPV.epicsPV(name+'.OFF', wait=False) } # Wait for all PVs to connect self.pvs['val'].pend_io() self.pvs['dmov'].setMonitor()
[docs] def move(self, value, relative=False, dial=False, step=False, ignore_limits=False): """ Moves a motor to an absolute position or relative to the current position in user, dial or step coordinates. :param float value: The absolute position or relative amount of the move :param bool relative: If True, move relative to current position. The default is an absolute move. :param bool dial: If True, _value_ is in dial coordinates. The default is user coordinates. :param bool step: If True, _value_ is in steps. The default is user coordinates. :param bool ignore_limits: If True, suppress raising exceptions if the move results in a soft or hard limit violation. :raises epicsMotorException: If software limit or hard limit violation detected, unless ignore_limits=True is set. .. note:: The "step" and "dial" keywords are mutually exclusive. The "relative" keyword can be used in user, dial or step coordinates. >>> m=epicsMotor('13BMD:m38') >>> m.move(10) # Move to position 10 in user coordinates >>> m.move(50, dial=True) # Move to position 50 in dial coordinates >>> m.move(2, step=True, relative=True) # Move 2 steps """ if dial: # Position in dial coordinates if relative: current = self.get_position(dial=True) self.pvs['dval'].putw(current+value) else: self.pvs['dval'].putw(value) elif step: # Position in steps if relative: current = self.get_position(step=True) self.pvs['rval'].putw(current + value) else: self.pvs['rval'].putw(value) else: # Position in user coordinates if relative: self.pvs['rlv'].putw(value) else: self.pvs['val'].putw(value) # Check for limit violations if not ignore_limits: self.check_limits()
[docs] def check_limits(self): """ Check wether there is a soft limit, low hard limit or high hard limit violation. :raises epicsMotorException: If software limit or hard limit violation detected. """ limit = self.pvs['lvio'].getw() if limit != 0: raise epicsMotorException('Soft limit violation') limit = self.pvs['lls'].getw() if limit != 0: raise epicsMotorException('Low hard limit violation') limit = self.pvs['hls'].getw() if limit != 0: raise epicsMotorException('High hard limit violation')
[docs] def stop(self): """ Immediately stops a motor from moving by writing 1 to the .STOP field. >>> m=epicsMotor('13BMD:m38') >>> m.move(10) # Move to position 10 in user coordinates >>> m.stop() # Stop motor """ self.pvs['stop'].putw(1)
[docs] def get_position(self, dial=False, readback=False, step=False): """ Returns the target or readback motor position in user, dial or step coordinates. :param bool readback: If True, return the readback position in the desired coordinate system. The default is to return the target position of the motor. :param bool dial: If True, return the position in dial coordinates. The default is user coordinates. :param bool step: If True, return the position in steps. The default is user coordinates. .. note:: The "step" and "dial" keywords are mutually exclusive. The "readback" keyword can be used in user, dial or step coordinates. >>> m = epicsMotor('13BMD:m38') >>> m.move(10) # Move to position 10 in user coordinates >>> pos_dial = m.get_position(dial=True) # Read the target position in dial coordinates >>> pos_step = m.get_position(readback=True, step=True) # Read the actual position in steps """ if dial: if readback: return self.pvs['drbv'].getw() else: return self.pvs['dval'].getw() elif step: if readback: return self.pvs['rrbv'].getw() else: return self.pvs['rval'].getw() else: if readback: return self.pvs['rbv'].getw() else: return self.pvs['val'].getw()
[docs] def set_position(self, position, dial=False, step=False): """ Sets the motor position in user, dial or step coordinates. :param float position: The new motor position :param bool dial: If True, set the position in dial coordinates. The default is user coordinates. :param bool step: If True, set the position in steps. The default is user coordinates. .. note:: The "step" and "dial" keywords are mutually exclusive. >>> m = epicsMotor('13BMD:m38') >>> m.set_position(10, dial=True) # Set the motor position to 10 in dial coordinates >>> m.set_position(1000, step=True) # Set the motor position to 1000 steps """ # Put the motor in "SET" mode self.pvs['set'].putw(1) if dial: self.pvs['dval'].putw(position) elif step: self.pvs['rval'].putw(position) else: self.pvs['val'].putw(position) # Put the motor back in "Use" mode self.pvs['set'].putw(0)
[docs] def wait(self, start=False, stop=False, poll=0.01, ignore_limits=False): """ Waits for the motor to start moving and/or stop moving. :param bool start: If True, wait for the motor to start moving. :param bool stop: If True, wait for the motor to stop moving. :param float poll: The time to wait between reading the .DMOV field of the record to see if the motor is moving. The default is 0.01 seconds. :param bool ignore_limits: If True, suppress raising an exception if a soft or hard limit is detected. :raises epicsMotorException: If software limit or hard limit violation detected, unless *ignore_limits=True* is set. .. note:: If neither the "start" nor "stop" keywords are set then "stop" is set to 1, so the routine waits for the motor to stop moving. If only "start" is set to 1 then the routine only waits for the motor to start moving. If both "start" and "stop" are set to 1 then the routine first waits for the motor to start moving, and then to stop moving. >>> m = epicsMotor('13BMD:m38') >>> m.move(50) # Move to position 50 >>> m.wait(start=True, stop=True) # Wait for the motor to start moving and then to stop moving """ if not start and not stop: stop = True if start: while self.pvs['dmov'].getw() == 1: time.sleep(poll) if stop: while self.pvs['dmov'].getw() == 0: time.sleep(poll) if not ignore_limits: self.check_limits()
[docs]class epicsMotorException(Exception): def __init__(self, message=''): self.message = message def __str__(self): return self.message
if __name__ == '__main__': import doctest doctest.testmod()