update phytron driver
+ fixes in secop_psi/sea.py (iodev)
This commit is contained in:
parent
f6f2dd189b
commit
e9f687e6dd
@ -5,15 +5,15 @@ description = phytron motor test
|
|||||||
[INTERFACE]
|
[INTERFACE]
|
||||||
uri = tcp://5000
|
uri = tcp://5000
|
||||||
|
|
||||||
[drv_iodev]
|
[drv_io]
|
||||||
description =
|
description =
|
||||||
class = secop_psi.phytron.PhytronIO
|
class = secop_psi.phytron.PhytronIO
|
||||||
uri = ma15-ts.psi.ch:3005
|
uri = ma10-ts.psi.ch:3004
|
||||||
# uri = serial:///dev/tty.usbserial?baudrate=57600
|
# uri = serial:///dev/tty.usbserial?baudrate=57600
|
||||||
# uri = serial:///dev/ttyUSB0?baudrate=9600
|
# uri = serial:///dev/ttyUSB0?baudrate=9600
|
||||||
|
|
||||||
[drv]
|
[drv]
|
||||||
description = a phytron motor
|
description = a phytron motor
|
||||||
class = secop_psi.phytron.Motor
|
class = secop_psi.phytron.Motor
|
||||||
iodev = drv_iodev
|
io = drv_io
|
||||||
encoder_mode = CHECK
|
encoder_mode = CHECK
|
||||||
|
@ -23,50 +23,62 @@
|
|||||||
"""driver for pythron motors"""
|
"""driver for pythron motors"""
|
||||||
|
|
||||||
from secop.core import Done, Command, EnumType, FloatRange, IntRange, \
|
from secop.core import Done, Command, EnumType, FloatRange, IntRange, \
|
||||||
HasIodev, Parameter, Property, Drivable, PersistentMixin, PersistentParam, StringIO, StringType
|
HasIO, Parameter, Property, Drivable, PersistentMixin, PersistentParam, StringIO, StringType
|
||||||
from secop.errors import CommunicationFailedError, HardwareError
|
from secop.errors import CommunicationFailedError, HardwareError
|
||||||
|
|
||||||
|
|
||||||
class PhytronIO(StringIO):
|
class PhytronIO(StringIO):
|
||||||
end_of_line = '\x03' # ETX
|
end_of_line = '\x03' # ETX
|
||||||
|
timeout = 0.2
|
||||||
identification = [('0IVR', 'MCC Minilog .*')]
|
identification = [('0IVR', 'MCC Minilog .*')]
|
||||||
|
|
||||||
def communicate(self, command):
|
def communicate(self, command, expect_response=True):
|
||||||
head, _, reply = super().communicate('\x02' + command).partition('\x02')
|
for ntry in range(5, 0, -1):
|
||||||
if reply[0] != '\x06': # ACK
|
try:
|
||||||
raise CommunicationFailedError('missing ACK %r' % reply)
|
head, _, reply = super().communicate('\x02' + command).partition('\x02')
|
||||||
|
if reply[0] == '\x06': # ACK
|
||||||
|
if len(reply) == 1 and expect_response:
|
||||||
|
raise CommunicationFailedError('empty response')
|
||||||
|
break
|
||||||
|
raise CommunicationFailedError('missing ACK %r' % reply)
|
||||||
|
except Exception as e:
|
||||||
|
if ntry == 1:
|
||||||
|
raise
|
||||||
|
self.log.warning('%s - retry', e)
|
||||||
return reply[1:]
|
return reply[1:]
|
||||||
|
|
||||||
|
|
||||||
class Motor(PersistentMixin, HasIodev, Drivable):
|
class Motor(PersistentMixin, HasIO, Drivable):
|
||||||
axis = Property('motor axis X or Y', StringType(), default='X')
|
axis = Property('motor axis X or Y', StringType(), default='X')
|
||||||
address = Property('address', IntRange(0, 15), default=0)
|
address = Property('address', IntRange(0, 15), default=0)
|
||||||
speed_factor = Property('steps / degree', FloatRange(0, None), default=2000)
|
speed_factor = Property('steps / degree', FloatRange(0, None), default=2000)
|
||||||
|
|
||||||
encoder_mode = Parameter('how to treat the encoder', EnumType('encoder', NO=0, READ=1, CHECK=2),
|
encoder_mode = Parameter('how to treat the encoder', EnumType('encoder', NO=0, READ=1, CHECK=2),
|
||||||
default=1, readonly=False)
|
default=1, readonly=False)
|
||||||
value = Parameter('angle', FloatRange(unit='deg'), poll=True)
|
value = Parameter('angle', FloatRange(unit='deg'))
|
||||||
target = Parameter('target angle', FloatRange(unit='deg'), readonly=False)
|
target = Parameter('target angle', FloatRange(unit='deg'), readonly=False)
|
||||||
speed = Parameter('', FloatRange(0, 20, unit='deg/s'), readonly=False, poll=True)
|
speed = Parameter('', FloatRange(0, 20, unit='deg/s'), readonly=False)
|
||||||
accel = Parameter('', FloatRange(2, 250, unit='deg/s/s'), readonly=False, poll=True)
|
accel = Parameter('', FloatRange(2, 250, unit='deg/s/s'), readonly=False)
|
||||||
encoder_tolerance = Parameter('', FloatRange(unit='deg'), readonly=False, default=0.01)
|
encoder_tolerance = Parameter('', FloatRange(unit='deg'), readonly=False, default=0.01)
|
||||||
zero = PersistentParam('', FloatRange(unit='deg'), readonly=False, default=0)
|
zero = PersistentParam('', FloatRange(unit='deg'), readonly=False, default=0)
|
||||||
encoder = Parameter('encoder reading', FloatRange(unit='deg'), poll=True)
|
encoder = Parameter('encoder reading', FloatRange(unit='deg'))
|
||||||
sameside_offset = Parameter('offset when always approaching from the same side',
|
sameside_offset = Parameter('offset when always approaching from the same side',
|
||||||
FloatRange(unit='deg'), readonly=True, default=0)
|
FloatRange(unit='deg'), readonly=False, default=0)
|
||||||
|
|
||||||
iodevClass = PhytronIO
|
ioClass = PhytronIO
|
||||||
fast_pollfactor = 0.02
|
fast_poll = 0.1
|
||||||
_sameside_pending = False
|
_sameside_pending = False
|
||||||
|
_mismatch_count = 0
|
||||||
|
|
||||||
def earlyInit(self):
|
def earlyInit(self):
|
||||||
|
super().earlyInit()
|
||||||
self.loadParameters()
|
self.loadParameters()
|
||||||
|
|
||||||
def get(self, cmd):
|
def get(self, cmd):
|
||||||
return self._iodev.communicate('\x02%x%s%s' % (self.address, self.axis, cmd))
|
return self.communicate('\x02%x%s%s' % (self.address, self.axis, cmd))
|
||||||
|
|
||||||
def set(self, cmd, value):
|
def set(self, cmd, value):
|
||||||
self._iodev.communicate('\x02%x%s%s%g' % (self.address, self.axis, cmd, value))
|
self.communicate('\x02%x%s%s%g' % (self.address, self.axis, cmd, value), False)
|
||||||
|
|
||||||
def set_get(self, cmd, value, query):
|
def set_get(self, cmd, value, query):
|
||||||
self.set(cmd, value)
|
self.set(cmd, value)
|
||||||
@ -79,6 +91,9 @@ class Motor(PersistentMixin, HasIodev, Drivable):
|
|||||||
enc = self.read_encoder()
|
enc = self.read_encoder()
|
||||||
else:
|
else:
|
||||||
enc = pos
|
enc = pos
|
||||||
|
status = self.communicate('\x02%xSE' % self.address)
|
||||||
|
status = status[0:4] if self.axis == 'X' else status[4:8]
|
||||||
|
self.log.debug('run %s enc %s end %s', status[1], status[2], status[3])
|
||||||
status = self.get('=H')
|
status = self.get('=H')
|
||||||
if status == 'N':
|
if status == 'N':
|
||||||
if self.encoder_mode == 'CHECK':
|
if self.encoder_mode == 'CHECK':
|
||||||
@ -86,9 +101,11 @@ class Motor(PersistentMixin, HasIodev, Drivable):
|
|||||||
if e1 - self.encoder_tolerance <= pos <= e2 + self.encoder_tolerance:
|
if e1 - self.encoder_tolerance <= pos <= e2 + self.encoder_tolerance:
|
||||||
self.status = self.Status.BUSY, 'driving'
|
self.status = self.Status.BUSY, 'driving'
|
||||||
else:
|
else:
|
||||||
self.log.error('encoder lag: %g not within %g..%g' % (pos, e1, e2))
|
self.log.error('encoder lag: %g not within %g..%g',
|
||||||
|
pos, e1, e2)
|
||||||
self.get('S') # stop
|
self.get('S') # stop
|
||||||
self.status = self.Status.ERROR, 'encoder lag error'
|
self.status = self.Status.ERROR, 'encoder lag error'
|
||||||
|
self.setFastPoll(False)
|
||||||
else:
|
else:
|
||||||
self.status = self.Status.BUSY, 'driving'
|
self.status = self.Status.BUSY, 'driving'
|
||||||
else:
|
else:
|
||||||
@ -99,9 +116,16 @@ class Motor(PersistentMixin, HasIodev, Drivable):
|
|||||||
return pos
|
return pos
|
||||||
if (self.encoder_mode == 'CHECK' and
|
if (self.encoder_mode == 'CHECK' and
|
||||||
abs(enc - pos) > self.encoder_tolerance):
|
abs(enc - pos) > self.encoder_tolerance):
|
||||||
self.status = self.Status.ERROR, 'encoder does not match pos'
|
if self._mismatch_count < 2:
|
||||||
|
self._mismatch_count += 1
|
||||||
|
else:
|
||||||
|
self.log.error('encoder mismatch: abs(%g - %g) < %g',
|
||||||
|
enc, pos, self.encoder_tolerance)
|
||||||
|
self.status = self.Status.ERROR, 'encoder does not match pos'
|
||||||
else:
|
else:
|
||||||
|
self._mismatch_count = 0
|
||||||
self.status = self.Status.IDLE, ''
|
self.status = self.Status.IDLE, ''
|
||||||
|
self.setFastPoll(False)
|
||||||
return pos
|
return pos
|
||||||
|
|
||||||
def read_encoder(self):
|
def read_encoder(self):
|
||||||
@ -113,8 +137,9 @@ class Motor(PersistentMixin, HasIodev, Drivable):
|
|||||||
return float(self.get('P14R')) / self.speed_factor
|
return float(self.get('P14R')) / self.speed_factor
|
||||||
|
|
||||||
def write_speed(self, value):
|
def write_speed(self, value):
|
||||||
if abs(float(self.get('P03R')) * self.speed_factor - 1) > 0.001:
|
inv_factor = float(self.get('P03R'))
|
||||||
raise HardwareError('speed factor does not match')
|
if abs(inv_factor * self.speed_factor - 1) > 0.001:
|
||||||
|
raise HardwareError('speed factor does not match %g' % (1.0 / inv_factor))
|
||||||
return float(self.set_get('P14S', int(value * self.speed_factor), 'P14R')) / self.speed_factor
|
return float(self.set_get('P14S', int(value * self.speed_factor), 'P14R')) / self.speed_factor
|
||||||
|
|
||||||
def read_accel(self):
|
def read_accel(self):
|
||||||
@ -136,6 +161,7 @@ class Motor(PersistentMixin, HasIodev, Drivable):
|
|||||||
self.set('A', value - self.zero + self.sameside_offset)
|
self.set('A', value - self.zero + self.sameside_offset)
|
||||||
else:
|
else:
|
||||||
self.set('A', value - self.zero)
|
self.set('A', value - self.zero)
|
||||||
|
self.setFastPoll(True, self.fast_poll)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def write_zero(self, value):
|
def write_zero(self, value):
|
||||||
@ -167,4 +193,4 @@ class Motor(PersistentMixin, HasIodev, Drivable):
|
|||||||
# TODO:
|
# TODO:
|
||||||
# '=E' electronics status
|
# '=E' electronics status
|
||||||
# '=I+' / '=I-': limit switches
|
# '=I+' / '=I-': limit switches
|
||||||
# use P37 to determine if restarted
|
# use P37 to determine if restarted
|
||||||
|
@ -62,7 +62,7 @@ service = %(service)s
|
|||||||
CFG_MODULE = """
|
CFG_MODULE = """
|
||||||
[%(module)s]
|
[%(module)s]
|
||||||
class = secop_psi.sea.%(modcls)s
|
class = secop_psi.sea.%(modcls)s
|
||||||
iodev = %(seaconn)s
|
io = %(seaconn)s
|
||||||
sea_object = %(module)s
|
sea_object = %(module)s
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -367,7 +367,7 @@ def get_datatype(paramdesc):
|
|||||||
|
|
||||||
|
|
||||||
class SeaModule(Module):
|
class SeaModule(Module):
|
||||||
iodev = Attached()
|
io = Attached()
|
||||||
|
|
||||||
# pollerClass=None
|
# pollerClass=None
|
||||||
path2param = None
|
path2param = None
|
||||||
@ -380,7 +380,7 @@ class SeaModule(Module):
|
|||||||
else:
|
else:
|
||||||
extra_modules = {}
|
extra_modules = {}
|
||||||
srv.extra_sea_modules = extra_modules
|
srv.extra_sea_modules = extra_modules
|
||||||
json_file = cfgdict.pop('json_file', None) or SeaClient.default_json_file[cfgdict['iodev']]
|
json_file = cfgdict.pop('json_file', None) or SeaClient.default_json_file[cfgdict['io']]
|
||||||
visibility_level = cfgdict.pop('visibility_level', 2)
|
visibility_level = cfgdict.pop('visibility_level', 2)
|
||||||
|
|
||||||
single_module = cfgdict.pop('single_module', None)
|
single_module = cfgdict.pop('single_module', None)
|
||||||
@ -518,7 +518,7 @@ class SeaModule(Module):
|
|||||||
# print('override %s.read_%s' % (cls.__name__, key))
|
# print('override %s.read_%s' % (cls.__name__, key))
|
||||||
|
|
||||||
def rfunc(self, cmd='hval %s/%s' % (base, path)):
|
def rfunc(self, cmd='hval %s/%s' % (base, path)):
|
||||||
reply = self._iodev.query(cmd)
|
reply = self.io.query(cmd)
|
||||||
try:
|
try:
|
||||||
reply = float(reply)
|
reply = float(reply)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -538,7 +538,7 @@ class SeaModule(Module):
|
|||||||
value = int(value)
|
value = int(value)
|
||||||
# TODO: check if more has to be done for valid tcl data (strings?)
|
# TODO: check if more has to be done for valid tcl data (strings?)
|
||||||
cmd = "%s %s" % (command, value)
|
cmd = "%s %s" % (command, value)
|
||||||
self._iodev.query(cmd)
|
self.io.query(cmd)
|
||||||
return Done
|
return Done
|
||||||
|
|
||||||
attributes['write_' + key] = wfunc
|
attributes['write_' + key] = wfunc
|
||||||
@ -584,7 +584,7 @@ class SeaModule(Module):
|
|||||||
self.DISPATCHER.broadcast_event(make_update(self.name, pobj))
|
self.DISPATCHER.broadcast_event(make_update(self.name, pobj))
|
||||||
|
|
||||||
def initModule(self):
|
def initModule(self):
|
||||||
self._iodev.register_obj(self, self.sea_object)
|
self.io.register_obj(self, self.sea_object)
|
||||||
super().initModule()
|
super().initModule()
|
||||||
|
|
||||||
|
|
||||||
@ -623,7 +623,7 @@ class SeaDrivable(SeaModule, Drivable):
|
|||||||
# return self.target
|
# return self.target
|
||||||
|
|
||||||
def write_target(self, value):
|
def write_target(self, value):
|
||||||
self._iodev.query('run %s %s' % (self.sea_object, value))
|
self.io.query('run %s %s' % (self.sea_object, value))
|
||||||
#self.status = [self.Status.BUSY, 'driving']
|
#self.status = [self.Status.BUSY, 'driving']
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@ -656,4 +656,4 @@ class SeaDrivable(SeaModule, Drivable):
|
|||||||
- on stdsct drivables this will call the halt script
|
- on stdsct drivables this will call the halt script
|
||||||
- on EaseDriv this will set the stopped state
|
- on EaseDriv this will set the stopped state
|
||||||
"""
|
"""
|
||||||
self._iodev.query('%s is_running 0' % self.sea_object)
|
self.io.query('%s is_running 0' % self.sea_object)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user