1
2 """SpecMotor module
3
4 This module defines the classes for motor objects
5
6 Classes:
7 SpecMotor -- class representing a motor in Spec
8 SpecMotorA -- class representing a motor in Spec, to be used with a GUI
9 """
10
11 __author__ = 'Matias Guijarro'
12 __version__ = '1.0'
13
14 import SpecConnectionsManager
15 import SpecEventsDispatcher
16 import SpecWaitObject
17 import SpecCommand
18 import logging
19 import types
20 import math
21
22 (NOTINITIALIZED, UNUSABLE, READY, MOVESTARTED, MOVING, ONLIMIT) = (0,1,2,3,4,5)
23 (NOLIMIT, LOWLIMIT, HIGHLIMIT) = (0,2,4)
24
26 """SpecMotorA class"""
27 - def __init__(self, specName = None, specVersion = None, callbacks={}):
28 """Constructor
29
30 Keyword arguments:
31 specName -- name of the motor in Spec (defaults to None)
32 specVersion -- 'host:port' string representing a Spec server to connect to (defaults to None)
33 """
34 self.motorState = NOTINITIALIZED
35 self.limit = NOLIMIT
36 self.limits = (None, None)
37 self.chanNamePrefix = ''
38 self.connection = None
39 self.__old_position = None
40
41
42 self.__callbacks = {
43 'connected': None,
44 'disconnected': None,
45 'motorLimitsChanged': None,
46 'motorPositionChanged': None,
47 'motorStateChanged': None
48 }
49 for cb_name in self.__callbacks.iterkeys():
50 if callable(callbacks.get(cb_name)):
51 self.__callbacks[cb_name] = SpecEventsDispatcher.callableObjectRef(callbacks[cb_name])
52
53 if specName is not None and specVersion is not None:
54 self.connectToSpec(specName, specVersion)
55 else:
56 self.specName = None
57 self.specVersion = None
58
59
61 """Connect to a remote Spec
62
63 Connect to Spec and register channels of interest for the specified motor
64
65 Arguments:
66 specName -- name of the motor in Spec
67 specVersion -- 'host:port' string representing a Spec server to connect to
68 """
69 self.specName = specName
70 self.specVersion = specVersion
71 self.chanNamePrefix = 'motor/%s/%%s' % specName
72
73 self.connection = SpecConnectionsManager.SpecConnectionsManager().getConnection(specVersion)
74 SpecEventsDispatcher.connect(self.connection, 'connected', self.__connected)
75 SpecEventsDispatcher.connect(self.connection, 'disconnected', self.__disconnected)
76
77
78
79
80 self.connection.registerChannel(self.chanNamePrefix % 'low_limit', self._motorLimitsChanged)
81 self.connection.registerChannel(self.chanNamePrefix % 'high_limit', self._motorLimitsChanged)
82 self.connection.registerChannel(self.chanNamePrefix % 'position', self.__motorPositionChanged, dispatchMode=SpecEventsDispatcher.FIREEVENT)
83 self.connection.registerChannel(self.chanNamePrefix % 'move_done', self.motorMoveDone, dispatchMode = SpecEventsDispatcher.FIREEVENT)
84 self.connection.registerChannel(self.chanNamePrefix % 'high_lim_hit', self.__motorLimitHit)
85 self.connection.registerChannel(self.chanNamePrefix % 'low_lim_hit', self.__motorLimitHit)
86 self.connection.registerChannel(self.chanNamePrefix % 'sync_check', self.__syncQuestion)
87 self.connection.registerChannel(self.chanNamePrefix % 'unusable', self.__motorUnusable)
88 self.connection.registerChannel(self.chanNamePrefix % 'offset', self.motorOffsetChanged)
89 self.connection.registerChannel(self.chanNamePrefix % 'sign', self.signChanged)
90
91
92 if self.connection.isSpecConnected():
93 self.__connected()
94
95
97 """Private callback triggered by a 'connected' event from Spec."""
98 self.connected()
99 if self.__callbacks.get("connected"):
100 cb = self.__callbacks["connected"]()
101 if cb is not None:
102 cb()
103
104
106 """Callback triggered by a 'connected' event from Spec
107
108 To be extended by derivated classes.
109 """
110 pass
111
112
114 """Private callback triggered by a 'disconnected' event from Spec
115
116 Put the motor in NOTINITIALIZED state.
117 """
118 self.__changeMotorState(NOTINITIALIZED)
119 self.disconnected()
120 if self.__callbacks.get("disconnected"):
121 cb = self.__callbacks["disconnected"]()
122 if cb is not None:
123 cb()
124
125
127 """Callback triggered by a 'disconnected' event from Spec
128
129 To be extended by derivated classes.
130 """
131 pass
132
133
134
135
136
137
140
141
144
145
147 self.motorLimitsChanged()
148 if self.__callbacks.get("motorLimitsChanged"):
149 cb = self.__callbacks["motorLimitsChanged"]()
150 if cb is not None:
151 cb()
152
153
155 """Callback triggered by a 'low_limit' or a 'high_limit' channel update,
156 or when the sign or offset for motor changes
157
158 To be extended by derivated classes.
159 """
160 pass
161
162
175
176
178 """Private callback triggered by a 'low_lim_hit' or a 'high_lim_hit' channel update
179
180 Update the motor state accordingly.
181
182 Arguments:
183 channelValue -- value of the channel
184 channelName -- name of the channel (either 'low_lim_hit' or 'high_lim_hit')
185 """
186 if channelValue:
187 if channelName.endswith('low_lim_hit'):
188 self.limit = self.limit | LOWLIMIT
189 self.__changeMotorState(ONLIMIT)
190 else:
191 self.limit = self.limit | HIGHLIMIT
192 self.__changeMotorState(ONLIMIT)
193
194
196 if self.__old_position is None:
197 self.__old_position = absolutePosition
198 else:
199 if math.fabs(absolutePosition - self.__old_position) > 1E-6:
200 self.__old_position = absolutePosition
201 else:
202 return
203 self.motorPositionChanged(absolutePosition)
204 if self.__callbacks.get("motorPositionChanged"):
205 cb = self.__callbacks["motorPositionChanged"]()
206 if cb is not None:
207 cb(absolutePosition)
208
209
210
212 """Callback triggered by a position channel update
213
214 To be extended by derivated classes.
215
216 Arguments:
217 absolutePosition -- motor absolute position
218 """
219 pass
220
221
223 """Set the motor offset value"""
224 c = self.connection.getChannel(self.chanNamePrefix % 'offset')
225
226 c.write(offset)
227
228
230 c = self.connection.getChannel(self.chanNamePrefix % 'offset')
231
232 return c.read()
233
234
236 c = self.connection.getChannel(self.chanNamePrefix % 'sign')
237
238 return c.read()
239
240
242 """Callback triggered by a 'sync_check' channel update
243
244 Call the self.syncQuestionAnswer method and reply to the sync. question.
245
246 Arguments:
247 channelValue -- value of the channel
248 """
249 if type(channelValue) == type(''):
250 steps = channelValue.split()
251 specSteps = steps[0]
252 controllerSteps = steps[1]
253
254 a = self.syncQuestionAnswer(specSteps, controllerSteps)
255
256 if a is not None:
257 c = self.connection.getChannel(self.chanNamePrefix % 'sync_check')
258 c.write(a)
259
260
262 """Answer to the sync. question
263
264 Return either '1' (YES) or '0' (NO)
265
266 Arguments:
267 specSteps -- steps measured by Spec
268 controllerSteps -- steps indicated by the controller
269 """
270 pass
271
272
285
286
288 """Private method for changing the SpecMotor object's internal state
289
290 Arguments:
291 state -- the motor state
292 """
293 self.motorState = state
294 self.motorStateChanged(state)
295 if self.__callbacks.get("motorStateChanged"):
296 cb = self.__callbacks["motorStateChanged"]()
297 if cb is not None:
298 cb(state)
299
300
301
303 """Callback to take into account a motor state update
304
305 To be extended by derivated classes
306
307 Arguments:
308 state -- the motor state
309 """
310 pass
311
312
313 - def move(self, absolutePosition):
314 """Move the motor to the required position
315
316 Arguments:
317 absolutePosition -- position to move to
318 """
319 if type(absolutePosition) != types.FloatType and type(absolutePosition) != types.IntType:
320 logging.getLogger("SpecClient").error("Cannot move %s: position '%s' is not a number", self.specName, absolutePosition)
321
322 self.__changeMotorState(MOVESTARTED)
323
324 c = self.connection.getChannel(self.chanNamePrefix % 'start_one')
325
326 c.write(absolutePosition)
327
328
331
332
341
342
344 """Stop the current motor
345
346 Send an 'abort' message to the remote Spec
347 """
348 self.connection.abort()
349
350
352 c = self.connection.getChannel("var/_MVC_CONTINUE_MOVING")
353 c.write(0)
354
355
357 c = self.connection.getChannel(self.chanNamePrefix % param)
358 return c.read()
359
360
362 c = self.connection.getChannel(self.chanNamePrefix % param)
363 c.write(value)
364
365
367 """Return the current position of the motor."""
368 c = self.connection.getChannel(self.chanNamePrefix % 'position')
369
370 return c.read()
371
372
374 """Return the current motor state."""
375 return self.motorState
376
377
379 """Return a (low limit, high limit) tuple in user units."""
380 lims = [x * self.getSign() + self.getOffset() for x in (self.connection.getChannel(self.chanNamePrefix % 'low_limit').read(), \
381 self.connection.getChannel(self.chanNamePrefix % 'high_limit').read())]
382 return (min(lims), max(lims))
383
384
386 """Return the motor dial position."""
387 c = self.connection.getChannel(self.chanNamePrefix % 'dial_position')
388
389 return c.read()
390
391
393 """Spec Motor"""
394 - def __init__(self, specName = None, specVersion = None, timeout = None):
395 """Constructor
396
397 Keyword arguments:
398 specName -- name of the motor in Spec (defaults to None)
399 specVersion -- 'host:port' string representing a Spec server to connect to (defaults to None)
400 timeout -- optional timeout for the connection (defaults to None)
401 """
402 self.chanNamePrefix = ''
403 self.connection = None
404
405 if specName is not None and specVersion is not None:
406 self.connectToSpec(specName, specVersion, timeout)
407 else:
408 self.specName = None
409 self.specVersion = None
410
411
430
431
433 """Return whether the motor is unusable or not."""
434 if self.connection is not None:
435 c = self.connection.getChannel(self.chanNamePrefix % 'unusable')
436
437 return c.read()
438
439
441 """Return if low limit has been hit."""
442 if self.connection is not None:
443 c = self.connection.getChannel(self.chanNamePrefix % 'low_lim_hit')
444
445 return c.read()
446
447
449 """Return if high limit has been hit."""
450 if self.connection is not None:
451 c = self.connection.getChannel(self.chanNamePrefix % 'high_lim_hit')
452
453 return c.read()
454
455
456 - def move(self, absolutePosition):
457 """Move the motor
458
459 Block until the move is finished
460
461 Arguments:
462 absolutePosition -- position where to move the motor to
463 """
464 if self.connection is not None:
465 c = self.connection.getChannel(self.chanNamePrefix % 'start_one')
466
467 c.write(absolutePosition)
468
469 w = SpecWaitObject.SpecWaitObject(self.connection)
470 w.waitChannelUpdate(self.chanNamePrefix % 'move_done', waitValue = 0)
471
472
475
476
478 if self.connection is not None:
479 cmdObject = SpecCommand.SpecCommandA("_mvc", self.connection)
480
481 if cmdObject.isSpecReady():
482 if limit:
483 cmdObject(self.specName, 1)
484 else:
485 cmdObject(self.specName, -1)
486
487
489 """Stop the current motor
490
491 Send an 'abort' message to the remote Spec
492 """
493 self.connection.abort()
494
495
497 if self.connection is not None:
498 c = self.connection.getChannel("var/_MVC_CONTINUE_MOVING")
499 c.write(0)
500
501
503 """Return the current absolute position for the motor."""
504 if self.connection is not None:
505 c = self.connection.getChannel(self.chanNamePrefix % 'position')
506
507 return c.read()
508
509
511 """Set the motor offset value"""
512 if self.connection is not None:
513 c = self.connection.getChannel(self.chanNamePrefix % 'offset')
514
515 c.write(offset)
516
517
519 if self.connection is not None:
520 c = self.connection.getChannel(self.chanNamePrefix % 'offset')
521
522 return c.read()
523
524
526 if self.connection is not None:
527 c = self.connection.getChannel(self.chanNamePrefix % 'sign')
528
529 return c.read()
530
531
533 if self.connection is not None:
534 c = self.connection.getChannel(self.chanNamePrefix % 'dial_position')
535
536 return c.read()
537
538
540 if self.connection is not None:
541 lims = [x * self.getSign() + self.getOffset() for x in (self.connection.getChannel(self.chanNamePrefix % 'low_limit').read(), \
542 self.connection.getChannel(self.chanNamePrefix % 'high_limit').read())]
543 return (min(lims), max(lims))
544