""" This module provides support for the EPICS motor record. Author: Mark Rivers Created: Sept. 16, 2002 Modifications: """ import time import epicsPV class epicsMotor: """ This module provides a class library for the EPICS motor record. It uses the epicsPV class, which is in turn a subclass of CaChannel. Virtual attributes: These attributes do not appear in the dictionary for this class, but are implemented with the __getattr__ and __setattr__ methods. They simply do getw() or putw(value) to the appropriate motor record fields. All attributes can be both read and written unless otherwise noted. Attribute 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 Exceptions: The check_limits() method raises an "epicsMotorException" if a soft limit or hard limit is detected. The move() and wait() methods call check_limits() before they return, unless they are called with the ignore_limits=1 keyword set. Example use: from epicsMotor import * m=epicsMotor('13BMD:m38') m.move(10) # Move to position 10 in user coordinates m.move(100, dial=1) # Move to position 100 in dial coordinates m.move(1, step=1, relative=1) # Move 1 step relative to current position m.wait() # Wait for motor to stop moving m.wait(start=1) # Wait for motor to start moving m.wait(start=1, stop=1) # 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 p=m.get_position() # Get the desired motor position in user coordinates p=m.get_position(dial=1) # Get the desired motor position in dial coordinates p=m.get_position(readback=1) # Get the actual position in user coordinates p=m.get_position(readback=1, step=1) Get the actual motor position in steps p=m.set_position(100) # Set the current position to 100 in user coordinates # Puts motor in Set mode, writes value, puts back in Use mode. p=m.set_position(10000, step=1) # Set the current position to 10000 steps """ def __init__(self, name): """ Creates a new epicsMotor instance. Inputs: name: The name of the EPICS motor record without any trailing period or field name. Example: m=epicsMotor('13BMD:m38') """ self.pvs = {'val' : epicsPV.epicsPV(name+'.VAL', wait=0), 'dval': epicsPV.epicsPV(name+'.DVAL', wait=0), 'rval': epicsPV.epicsPV(name+'.RVAL', wait=0), 'rlv' : epicsPV.epicsPV(name+'.RLV', wait=0), 'rbv' : epicsPV.epicsPV(name+'.RBV', wait=0), 'drbv': epicsPV.epicsPV(name+'.DRBV', wait=0), 'rrbv': epicsPV.epicsPV(name+'.RRBV', wait=0), 'dmov': epicsPV.epicsPV(name+'.DMOV', wait=0), 'stop': epicsPV.epicsPV(name+'.STOP', wait=0), 'velo': epicsPV.epicsPV(name+'.VELO', wait=0), 'vbas': epicsPV.epicsPV(name+'.VBAS', wait=0), 'accl': epicsPV.epicsPV(name+'.ACCL', wait=0), 'desc': epicsPV.epicsPV(name+'.DESC', wait=0), 'mres': epicsPV.epicsPV(name+'.MRES', wait=0), 'hlm': epicsPV.epicsPV(name+'.HLM', wait=0), 'llm': epicsPV.epicsPV(name+'.LLM', wait=0), 'dhlm': epicsPV.epicsPV(name+'.DHLM', wait=0), 'dllm': epicsPV.epicsPV(name+'.DLLM', wait=0), 'bdst': epicsPV.epicsPV(name+'.BDST', wait=0), 'set': epicsPV.epicsPV(name+'.SET', wait=0), 'lvio': epicsPV.epicsPV(name+'.LVIO', wait=0), 'lls': epicsPV.epicsPV(name+'.LLS', wait=0), 'hls': epicsPV.epicsPV(name+'.HLS', wait=0), 'off': epicsPV.epicsPV(name+'.OFF', wait=0) } # Wait for all PVs to connect self.pvs['val'].pend_io() def move(self, value, relative=0, dial=0, step=0, ignore_limits=0): """ Moves a motor to an absolute position or relative to the current position in user, dial or step coordinates. Inputs: value: The absolute position or relative amount of the move Keywords: relative: Set relative=1 to move relative to current position. The default is an absolute move. dial: Set dial=1 if "value" is in dial coordinates. The default is user coordinates. step: Set step=1 if "value" is in steps. The default is user coordinates. ignore_limits: Set ignore_limits=1 to suppress raising exceptions if the move results in a soft or hard limit violation. Notes: The "step" and "dial" keywords are mutually exclusive. The "relative" keyword can be used in user, dial or step coordinates. Examples: m=epicsMotor('13BMD:m38') m.move(10) # Move to position 10 in user coordinates m.move(100, dial=1) # Move to position 100 in dial coordinates m.move(2, step=1, relative=1) # Move 2 steps """ if (dial != 0): # Position in dial coordinates if (relative != 0): current = self.get_position(dial=1) self.pvs['dval'].putw(current+value) else: self.pvs['dval'].putw(value) elif (step != 0): # Position in steps if (relative != 0): current = self.get_position(step=1) self.pvs['rval'].putw(current + value) else: self.pvs['rval'].putw(value) else: # Position in user coordinates if (relative != 0): self.pvs['rlv'].putw(value) else: self.pvs['val'].putw(value) # Check for limit violations if (ignore_limits == 0): self.check_limits() def check_limits(self): 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') def stop(self): """ Immediately stops a motor from moving by writing 1 to the .STOP field. Examples: m=epicsMotor('13BMD:m38') m.move(10) # Move to position 10 in user coordinates m.stop() # Stop motor """ self.pvs['stop'].putw(1) def get_position(self, dial=0, readback=0, step=0): """ Returns the target or readback motor position in user, dial or step coordinates. Keywords: readback: Set readback=1 to return the readback position in the desired coordinate system. The default is to return the target position of the motor. dial: Set dial=1 to return the position in dial coordinates. The default is user coordinates. step: Set step=1 to return the position in steps. The default is user coordinates. Notes: The "step" and "dial" keywords are mutually exclusive. The "readback" keyword can be used in user, dial or step coordinates. Examples: m=epicsMotor('13BMD:m38') m.move(10) # Move to position 10 in user coordinates p=m.get_position(dial=1) # Read the target position in # dial coordinates p=m.get_position(readback=1, step=1) # Read the actual position in # steps """ if (dial != 0): if (readback != 0): return self.pvs['drbv'].getw() else: return self.pvs['dval'].getw() elif (step != 0): if (readback != 0): return self.pvs['rrbv'].getw() else: return self.pvs['rval'].getw() else: if (readback != 0): return self.pvs['rbv'].getw() else: return self.pvs['val'].getw() def set_position(self, position, dial=0, step=0): """ Sets the motor position in user, dial or step coordinates. Inputs: position: The new motor position Keywords: dial: Set dial=1 to set the position in dial coordinates. The default is user coordinates. step: Set step=1 to set the position in steps. The default is user coordinates. Notes: The "step" and "dial" keywords are mutually exclusive. Examples: m=epicsMotor('13BMD:m38') m.set_position(10, dial=1) # Set the motor position to 10 in # dial coordinates m.set_position(1000, step=1) # Set the motor position to 1000 steps """ # Put the motor in "SET" mode self.pvs['set'].putw(1) if (dial != 0): self.pvs['dval'].putw(position) elif (step != 0): self.pvs['rval'].putw(position) else: self.pvs['val'].putw(position) # Put the motor back in "Use" mode self.pvs['set'].putw(0) def wait(self, start=0, stop=0, poll=0.01, ignore_limits=0): """ Waits for the motor to start moving and/or stop moving. Keywords: start: Set start=1 to wait for the motor to start moving. stop: Set stop=1 to wait for the motor to stop moving. poll: Set this keyword to 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. ignore_limits: Set ignore_limits=1 to suppress raising an exception if a soft or hard limit is detected Notes: 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. Examples: m=epicsMotor('13BMD:m38') m.move(100) # Move to position 100 m.wait(start=1, stop=1) # Wait for the motor to start moving # and then to stop moving """ if (start == 0) and (stop == 0): stop=1 if (start != 0): while(1): done = self.pvs['dmov'].getw() if (done != 1): break time.sleep(poll) if (stop != 0): while(1): done = self.pvs['dmov'].getw() if (done != 0): break time.sleep(poll) if (ignore_limits == 0): self.check_limits() def __getattr__(self, attrname): if (attrname == 'slew_speed'): return self.pvs['velo'].getw() elif (attrname == 'base_speed'): return self.pvs['vbas'].getw() elif (attrname == 'acceleration'): return self.pvs['accl'].getw() elif (attrname == 'description'): return self.pvs['desc'].getw() elif (attrname == 'resolution'): return self.pvs['mres'].getw() elif (attrname == 'high_limit'): return self.pvs['hlm'].getw() elif (attrname == 'low_limit'): return self.pvs['llm'].getw() elif (attrname == 'dial_high_limit'): return self.pvs['dhlm'].getw() elif (attrname == 'dial_low_limit'): return self.pvs['dllm'].getw() elif (attrname == 'backlash'): return self.pvs['bdst'].getw() elif (attrname == 'offset'): return self.pvs['off'].getw() elif (attrname == 'done_moving'): return self.pvs['dmov'].getw() else: raise AttributeError(attrname) def __setattr__(self, attrname, value): if (attrname == 'pvs'): self.__dict__[attrname]=value elif (attrname == 'slew_speed'): self.pvs['velo'].putw(value) elif (attrname == 'base_speed'): self.pvs['vbas'].putw(value) elif (attrname == 'acceleration'): self.pvs['accl'].putw(value) elif (attrname == 'description'): self.pvs['desc'].putw(value) elif (attrname == 'resolution'): self.pvs['mres'].putw(value) elif (attrname == 'high_limit'): self.pvs['hlm'].putw(value) elif (attrname == 'low_limit'): self.pvs['llm'].putw(value) elif (attrname == 'dial_high_limit'): self.pvs['dhlm'].putw(value) elif (attrname == 'dial_low_limit'): self.pvs['dllm'].putw(value) elif (attrname == 'backlash'): self.pvs['bdst'].putw(value) elif (attrname == 'offset'): self.pvs['off'].putw(value) else: raise AttributeError(attrname) class epicsMotorException(Exception): def __init__(self, args=None): self.args=args