1 import logging
2 import serial
3
5 """Base class for exceptions in this module."""
6 pass
7
9 """Exception raised if the Cortex controller responds to a command
10 with 'E#/r/n', i.e. if it receives and illegal command. This should
11 of course never happen...
12
13 Attributes: command - the command string sent to the controller which
14 resulted in the error."""
16 self.command = command
17
19 """Exception raised if there is an attempt to set a Cortex parameter
20 with an value outside the allowed range.
21
22 Attributes: parameter - the parameter in question
23 value - the illegal value requested
24 range - the allowed range for the parameter"""
25 - def __init__(self, parameter, value, range):
26 self.parameter = parameter
27 self.value = value
28 self.range = range
29
31 """Exception raised when there is no response from the Cortex
32 controller. This most likely means it's been disconnected,
33 switched off, or a command has been sent to a non-existant unit
34 in a daisy chain.
35
36 Attributes: command - the last command string sent to the controller"""
38 self.commmand = command
39
41 """Exception raised when an unexpected response is received from the
42 Cortex controller. This should of course never happen...
43
44 Attributes: command - the command string sent to the controller
45 response - the response string received"""
47 self.command = command
48 self.response = response
49
51 """Basic class representing an interface to a Cortex controller
52 (or daisy chain of controllers). Presents functions corresponding
53 to each of the commands accepted by a Cortex controller. Performs
54 range checking on arguments and returns response from the
55 controller."""
56
57 - def __init__(self, portname, units, name='cortex'):
58 """
59 Arguments:
60
61 portname: device name of the serial port to use
62 units: number of cortex units in the chain
63 name: name to be used as a label in the logs
64 """
65 self.portname = portname
66 self.units = int(units)
67 self.name = name
68 self.logger = logging.getLogger(name)
69
70
71 try:
72 self.__port = serial.Serial(portname, timeout=5)
73 except serial.SerialException:
74
75 self.logger.critical('Failed to open serial port %s!' % portname)
76 return
77 self.logger.info('Connected to serial port %s' % portname)
78
79
80 self.__port.write('\r\n')
81 self.__setNewline(True, cortex_num=1)
82
83 for unit in range(1, self.units + 1):
84 self.setCurrent(0, unit)
85 self.setOutput(0, unit)
86
88 """Function used to send any command that expects the basic
89 '#\r\n' response, and parses the response received. If
90 the standard response is not received an appropriate
91 exception is raised.
92
93 Arguments: command - string containing the command to send."""
94 self.__port.flushInput()
95 self.__port.write(command)
96 response = self.__port.readline()
97 if response == "#\r\n":
98 return
99 elif response == "E\r\n":
100
101 self.logger.critical('Syntax error - sent ' + repr(command))
102 elif not response:
103
104 self.logger.critical('No response on port %s - sent ' \
105 % self.portname + repr(command))
106 else:
107
108 self.logger.critical('Unexpected response ' + repr(response) + \
109 ' - sent ' + repr(command))
110
112 """Function used to send any command that expects an integer
113 N in a 'N#\r\n' response, parses the response received, and returns
114 the integer. If the expected response is not received an appropriate
115 exception is raised.
116
117 Arguments: command - string containing the command to send."""
118 self.__port.flushInput()
119 self.__port.write(command)
120 response = self.__port.readline()
121 if response[-3:] == "#\r\n":
122 return int(response[:-3])
123 elif response == "E\r\n":
124
125 self.logger.critical('Syntax error - sent "%s"' % command)
126 elif not response:
127
128 self.logger.critical('No response on port %s - sent "%s"' \
129 % (self.portname, command))
130 else:
131
132 self.logger.critical('Unexpected response "%s" - sent "%s"' %
133 (response, command))
134
149
151 """Queries the limit switch status, returns -1 if lower limit made,
152 +1 if upper limit made and 0 is neither is. Note, this command
153 ALWAYS stops the motors, use isActive() first to determine if the
154 motors are in motion.
155
156 Arguments: cortex_num - which controller of a daisy chain to
157 query, default 1."""
158 if cortex_num > self.units or cortex_num < 1:
159 self.logger.warning('Illegal unit number in getLimitStatus(%i)'\
160 % cortex_num)
161 else:
162 self.logger.debug('getLimitStatus(%i) called' % cortex_num)
163 return self.__intCommand("%iRL\n" % cortex_num)
164
166 if cortex_num > self.units or cortex_num < 1:
167 self.logger.warning('Illegal unit number in getParams(%i)'\
168 % cortex_num)
169 else:
170 self.logger.debug('getParams(%i) called' % cortex_num)
171 self.__port.flushInput()
172 self.__port.write("%i?\n" % cortex_num)
173 return self.__port.readline()
174
176 """Requests the current motor position from the controller,
177 in steps from the datum and returns it as an integer.
178
179 Arguments: cortex_num - which controller of a daisy chain to
180 query, default 1."""
181 if cortex_num > self.units or cortex_num < 1:
182 self.logger.warning('Illegal unit number in getPosition(%i)'\
183 % cortex_num)
184 else:
185 self.logger.debug('getPosition(%i) called' % cortex_num)
186 return self.__intCommand("%iW\n" % cortex_num)
187
188 - def getRemaining(self, cortex_num=1):
189 """Requests the number of steps left to go in the current move
190 from the controller and returns the reply as an integer.
191
192 Arguments: cortex_num - which controller of a daisy chain to
193 query, default 1."""
194 if cortex_num > self.units or cortex_num < 1:
195 self.logger.warning('Illegal unit number in getRemaining(%i)'\
196 % cortex_num)
197 else:
198 self.logger.debug('getRemaining(%i) called' % cortex_num)
199 return self.__intCommand("%iRS\n" % cortex_num)
200
202 """Requests the motor status from the controller, returns True
203 if the motor is active, False otherwise.
204
205 Arguments: cortex_num - which controller of a daisy chain to
206 query, default 1."""
207 if cortex_num > self.units or cortex_num < 1:
208 self.logger.warning('Illegal unit number in isActive(%i)'\
209 % cortex_num)
210 else:
211 self.logger.debug('isActive(%i) called' % cortex_num)
212 return bool(self.__intCommand("%iA\n" % cortex_num))
213
215 """Writes all current controller parameters to EEPROM, if
216 fitted.
217
218 Arguments: cortex_num - which controller of a daisy chain to
219 command, default 1."""
220 if cortex_num > self.units or cortex_num < 1:
221 self.logger.warning('Illegal unit number in saveParams(%i)'\
222 % cortex_num)
223 else:
224 self.logger.debug('saveParams(%i) called' % cortex_num)
225 self.__basicCommand("%iQ\n" % cortex_nun)
226
228 """Sets the motor current, in mA. Allowed range is
229 0-600mA, in steps of 40mA (intermediate values, if given,
230 are rounded down to the nearest multiple of 40). N.B.
231 Do not exceed 160mA in continous use unless a heatsink is
232 fitted.
233
234 Arguments: current - motor current, in mA, integer in range
235 0-600.
236 cortex_num - which controller of a daisy chain to
237 command, default 1."""
238 if cortex_num > self.units or cortex_num < 1:
239 self.logger.warning('Illegal unit number in setCurrent(%i, %i)'\
240 % (current, cortex_num))
241 return
242 if current < 0 or current > 600:
243
244 self.logger.warning('Illegal current in setCurrent(%i, &i)'\
245 % (current, cortex_num))
246 else:
247 self.logger.debug('setCurrent(%i, %i) called' \
248 % (current, cortex_num))
249 self.__basicCommand("%iE %i\n" % (cortex_num, current/40))
250
251 - def setDatum(self, position=0, cortex_num=1):
252 """Change the coordinate system so that the current position
253 is equal to the argument.
254
255 Arguments: position - value to assign to the current position,
256 default 0.
257 cortex_num - which controller of a daisy chain to
258 command, default 1."""
259 if cortex_num > self.units or cortex_num < 1:
260 self.logger.warning('Illegal unit number in setDatum(%i, %i)'\
261 % (position, cortex_num))
262 else:
263 self.logger.debug('setDatum(%i, %i) called' \
264 % (position, cortex_num))
265 self.__basicCommand("%iD %i\n" % (cortex_num, position))
266
268 """Return all parameters to their default values, with the
269 exception of the newline parameter on the first controller in
270 the chain (newlines are required for communication using
271 pySerial). The default values are given in the Cortex
272 controller doumentation.
273
274 Arguments: cotrex_num - which controller of a daisy chain to
275 reset to defaults, default 1."""
276
277 if cortex_num > self.units or cortex_num < 1:
278 self.logger.warning('Illegal unit number in setDefaults(%i)'\
279 % cortex_num)
280 return
281 self.logger.debug('setDefaults(%i) called' % cortex_num)
282 if cortex_num == 1:
283 command = "1F\n"
284 self.__port.flushInput()
285 self.__port.write(command)
286 response = self.__port.readline()
287 if response == "#\r":
288 self.__setNewline(True, cortex_num=1)
289 elif response == "E\r":
290 self.logger.critical('Syntax error - sent "%s"' % command)
291
292 elif not response:
293 self.logger.critical('No response on port %s - sent "%s"' \
294 % (self.portname, command))
295
296 else:
297 self.logger.critical('Unexpected response "%s" - sent "%s"' %
298 (response, command))
299
300 else:
301 self.__basicCommand("%iF\n" % cortex_num)
302
303 - def setJoystick(self, jitter=4, deadband=20, slowband=10, sense=0, \
304 cortex_num=1):
305 """Sets the joystick parameters. No sanity checking of
306 arguments implemented yet.
307
308 Arguments: jitter - allowable joystick noise, default 4
309 deadband - joystick deadband, default 20
310 slowband - slow motion band, default 10
311 sense - joystick not reversed, default 0
312 cortex_num - which controller in a daisy chain
313 to command, default 1."""
314 if cortex_num > self.units or cortex_num < 1:
315 self.logger.warning(\
316 'Illegal unit number in setJoystick(%i, %i, %i, $i, %i)'\
317 % (jitter, deadband, slowband, sense, cortex_num))
318 else:
319 self.logger.debug(\
320 'setJoystick(%i, %i, %i, %i, %i) called'\
321 % (jitter, deadband, slowband, sense, cortex_num))
322 self.__basicCommand("%iJ %i %i %i %i\n" % \
323 (cortex_num, jitter, deadband, slowband, \
324 sense))
325
327 """Toggles local/manual mode. If the first argument is
328 True sets local mode on, if False sets local mode off.
329
330 Arguments: yes - boolean, if True set local mode on,
331 and vice versa, default True
332 cortex_num - which controller in a daisy chain
333 to command, default 1."""
334 if cortex_num > self.units or cortex_num < 1:
335 self.logger.warning('Illegal unit number in setLocalMode(%i, %i)'\
336 % (yes, cortex_num))
337 return
338 self.logger.debug('setLocalMode(%i, %i) called' % (yes, cortex_num))
339 if yes:
340 self.__basicCommand("%iL 1\n" % cortex_num)
341 else:
342 self.__basicCommand("%iL 0\n" % cortex_num)
343
345 """Set the microstepping ratio using the numeric codes
346 described in the Cortex controller documentation. The
347 argument ratio should be an integer in the range 0-63.
348
349 Arguments: ratio - microstepping ratio code, integer
350 in range 0-63.
351 cortex_num - which controller in a diasy chain
352 to set, default 1."""
353 if cortex_num > self.units or cortex_num < 1:
354 self.logger.warning(\
355 'Illegal unit number in setMicrostepping(%i, %i)' \
356 % (ratio, cortex_num))
357 return
358 if ratio < 0 or ratio > 63:
359
360 self.logger.warning(\
361 'Illegal ratio in setMicrostepping(%i, %i)' \
362 % (ratio, cortex_num))
363 else:
364 self.logger.debug('setMicrostepping(%i, %i) called'\
365 % (ratio, cortex_num))
366 self.__basicCommand("%iR %i\n" % (cortex_num, ratio))
367
368 - def setMotorParams(self, baseSpeed = 1000, maxSpeed = 60000, \
369 acceleration = 30000, deceleration = 120000, \
370 cortex_num=1):
371 """Sets the motor motion parameters, base speed, maximum
372 speed, acceleration and deceleration.
373
374 Arguments: baseSpeed - starting speed for move, moveAbs
375 and moveLimit commands, default
376 1000.
377 maxSpeed - top speed, default 60000
378 acceleration - acceleration, default 30000
379 deceleration - deceleration, default 120000
380 cortex_num - which controller in a daisy chain
381 to set, default 1."""
382 if cortex_num > self.units or cortex_num < 1:
383 self.logger.warning(\
384 'Illegal unit number in setMotorParams(%i, %i, %i, %i, %i)'\
385 % (baseSpeed, maxSpeed, acceleration, \
386 deceleration, cortex_num))
387 else:
388 self.logger.debug(\
389 'setMotorParams(%i, %i, %i, %i, %i) called'\
390 % (baseSpeed, maxSpeed, acceleration, \
391 deceleration, cortex_num))
392 self.__basicCommand("%iP%i %i %i %i\n" % \
393 (cortex_num, baseSpeed, maxSpeed, \
394 acceleration, deceleration))
395
397 """Toggles the addition of newlines to the replies from the
398 Cortex controller. If the first argument is True sets
399 newlines on, if False sets newlines off. In order for
400 proper communications with the first cortex controller
401 in the chain it must have newlines set to True. For
402 inter-controller communications to work all other
403 controllers (if present) should have newlines False.
404
405 Arguments: yes - boolean, if True set local mode on,
406 and vice versa, default True
407 cortex_num - which controller in a daisy chain
408 to command, default 1."""
409 if cortex_num > self.units or cortex_num < 1:
410 self.logger.warning(\
411 'Illegal unit number in __setNewline(%i, %i)' \
412 % (yes, cortex_num))
413 return
414 self.logger.debug('__setNewline(%i, %i) called' \
415 % (yes, cortex_num))
416 if yes:
417 self.__basicCommand("%iN 1\n" % cortex_num)
418 else:
419 self.__basicCommand("%iN 0\n" % cortex_num)
420
422 """Sets the state of the four bit optocoupler output
423 of the Cortex controller.
424
425 Arguments: output - integer in range 0-15 corresponding
426 to desired state of the 4 bit output.
427 cortex_num - which controller in a chain to
428 set, default 1."""
429 if cortex_num > self.units or cortex_num < 1:
430 self.logger.warning(\
431 'Illegal unit number in setOutput(%i, %i)' \
432 % (output, cortex_num))
433 return
434 if output < 0 or output > 15:
435
436 self.logger.warning(\
437 'Illegal output in setOutput(%i, %i)' \
438 % (output, cortex_num))
439 else:
440 self.logger.debug('setOutput(%i, %i) called' \
441 % (output, cortex_num))
442 self.__basicCommand("%iO %i\n" % (cortex_num, output))
443
444 - def setSpeed(self, speed=150, cortex_num=1):
445 """Sets the constant speed for moveConst, moveConstAbs
446 and moveConstLimit commands.
447
448 Arguments: speed - required speed in steps/sec,
449 integer in range 62-60000
450 cortex_num - which controller in a chain
451 to set, default 1."""
452 if cortex_num > self.units or cortex_num < 1:
453 self.logger.warning(\
454 'Illegal unit number in setSpeed(%i, %i)' \
455 % (speed, cortex_num))
456 return
457 if speed < 62 or speed > 60000:
458
459 self.logger.warning(\
460 'Illegal speed in setSpeed(%i, %i)' \
461 % (speed, cortex_num))
462 else:
463 self.logger.debug('setSpeed(%i, %i) called' \
464 % (speed, cortex_num))
465 self.__basicCommand("%iC %i\n" % (cortex_num, speed))
466
467 - def halt(self, cortex_num=1):
468 """Smoothly halts the motor. For emergency stops use
469 the stop command.
470
471 Arguments: cortex_num - which controller in a chain
472 to halt, default 1."""
473 if cortex_num > self.units or cortex_num < 1:
474 self.logger.warning(\
475 'Illegal unit number in halt(%i)' % cortex_num)
476 else:
477 self.logger.debug('halt(%i) called' % cortex_num)
478 self.__basicCommand("%iH\n" % cortex_num)
479
480 - def move(self, steps, cortex_num=1):
481 """Move a given number of steps (<2000000000) with
482 acceleration (uses motion parameters defined by
483 setMotorParams).
484
485 Arguments: steps - number of steps to move, integer in
486 range -2000000000:+2000000000
487 cortex_num - which controller to instruct to
488 move, default 1."""
489 if cortex_num > self.units or cortex_num < 1:
490 self.logger.warning('Illegal unit number in move(%i, %i)' \
491 % (steps, cortex_num))
492 return
493 if steps < -2000000000 or steps > 2000000000:
494
495 self.logger.warning('Illegal steps in move(%i, %i)' \
496 % (steps, cortex_num))
497 else:
498 self.logger.debug('move(%i, %i) called' \
499 % (steps, cortex_num))
500 self.__basicCommand("%iM %i\n" % (cortex_num, steps))
501
502 - def moveAbs(self, position, cortex_num=1):
503 """Move to a given position, with acceleration (uses motion
504 parameters defined by setMotorParams).
505
506 Arguments: position - position, measure in steps from the datum
507 to move to.
508 cortex_num - which controller to instruct to
509 move, default 1."""
510 if cortex_num > self.units or cortex_num < 1:
511 self.logger.warning('Illegal unit number in moveAbs(%i, %i)' \
512 % (position, cortex_num))
513 else:
514 self.logger.debug('moveAbs(%i, %i) called' \
515 % (position, cortex_num))
516 self.__basicCommand("%iMA %i\n" % (cortex_num, position))
517
519 """Move a given number of steps (<2000000000) at a
520 constant speed (uses speed set via setSpeed).
521
522 Arguments: steps - number of steps to move, integer in
523 range -2000000000:+2000000000
524 cortex_num - which controller to instruct to
525 move, default 1."""
526 if cortex_num > self.units or cortex_num < 1:
527 self.logger.warning('Illegal unit number in moveConst(%i, %i)' \
528 % (steps, cortex_num))
529 return
530 if steps < -2000000000 or steps > 2000000000:
531
532 self.logger.warning('Illegal steps in moveConst(%i, %i)' \
533 % (steps, cortex_num))
534 else:
535 self.logger.debug('moveConst(%i, %i) called' \
536 % (steps, cortex_num))
537 self.__basicCommand("%iMC %i\n" % (cortex_num, steps))
538
540 """Move to a given position at a constant speed (used
541 speed set via setSpeed).
542
543 Arguments: position - position, measure in steps from the datum
544 to move to.
545 cortex_num - which controller to instruct to
546 move, default 1."""
547 if cortex_num > self.units or cortex_num < 1:
548 self.logger.warning('Illegal unit number in moveConstAbs(%i, %i)'\
549 % (position, cortex_num))
550 else:
551 self.logger.debug('moveConstAbs(%i, %i) called' \
552 % (position, cortex_num))
553 self.__basicCommand("%iMCA %i\n" % (cortex_num, position))
554
556 """Move to a given limit at a constant speed (uses speed
557 set via setSpeed).
558
559 Arguments: direction - the direction to move in, 1 for
560 towards the upper limit, -1 for
561 towards the lower limit, or 0
562 for, er, something else (Cortex
563 documentation doesn't explain this).
564 cortex_num - which controller to instruct to
565 move, default 1."""
566 if cortex_num > self.units or cortex_num < 1:
567 self.logger.warning(\
568 'Illegal unit number in moveConstLimit(%i, %i)'\
569 % (direction, cortex_num))
570 if direction < -1 or direction > 1:
571
572 self.logger.warning('Illegal direction in moveConstLimit(%i, %i)'\
573 % (direction, cortex_num))
574 else:
575 self.logger.debug('moveConstLimit(%i, %i) called' \
576 % (direction, cortex_num))
577 self.__basicCommand("%iMCL %i\n" & (cortex_num, direction))
578
579 - def moveLimit(self, direction, cortex_num=1):
580 """Move to a given limit with acceleration (uses motion
581 parameters defined with setMotorParams).
582
583 Arguments: direction - the direction to move in, 1 for
584 towards the upper limit, -1 for
585 towards the lower limit, or 0
586 for, er, something else (Cortex
587 documentation doesn't explain this).
588 cortex_num - which controller to instruct to
589 move, default 1."""
590 if cortex_num > self.units or cortex_num < 1:
591 self.logger.warning(\
592 'Illegal unit number in moveLimit(%i, %i)'\
593 % (direction, cortex_num))
594 if direction < -1 or direction > 1:
595
596 self.logger.warning('Illegal direction in moveLimit(%i, %i)'\
597 % (direction, cortex_num))
598 else:
599 self.logger.debug('moveLimit(%i, %i) called' \
600 % (direction, cortex_num))
601 self.__basicCommand("%iML %i\n" % (cortex_num, direction))
602
603 - def stop(self, cortex_num=1):
604 """Emergency stop.
605
606 Arguments: cortex_num - which controller in a chain to stop,
607 default 1."""
608 if cortex_num > self.units or cortex_num < 1:
609 self.logger.warning('Illegal unit number in stop(%i)' % cortex_num)
610 else:
611 self.logger.debug('stop(%i) called' % cortex_num)
612 self.__basicCommand("%iS\n" % cortex_num)
613
614 - def trigger(self, input=-1, cortex_num=1):
615 """Delay move until given a given input goes high. If no
616 input is specified then moves occur as they are requested.
617
618 Arguments: input - the number of the input to trigger on,
619 integer in the range 0-3 (I think). If
620 omitted triggering is suspended.
621 cortex_num - which controller in a chain to set
622 triggering for, default 1."""
623 if cortex_num > self.units or cortex_num < 1:
624 self.logger.warning('Illegal unit number in trigger(%i, %i)' \
625 % (input, cortex_num))
626 return
627 if input < -1 or input > 3:
628
629 self.logger.warning('Illegal input in trigger(%i, %i)' \
630 % (input, cortex_num))
631 elif input == -1:
632 self.__basicCommand("%iT\n" % cortex_num)
633 self.logger.debug('trigger(cortex_num=%i) called' % cortex_num)
634 else:
635 self.__basicCommand("%iT %i\n" % (cortex_num, input))
636 self.logger.debug('trigger(%i, %i) called' % (trigger, cortex_num))
637