RIG InMoov Project
servo_demo.py
1 """! Documentation for the servo_demo.py script.
2 An example script for controlling the right hand.
3 This script can also be used to calibrate the bot. Please see @ref connect,
4 @ref cmd, and @ref calibrate.
5 This script requires python with a library called pyserial.
6 After instally python 3, pyserial can be installed by calling
7 
8 @code{.sh}
9 pip install pyserial
10 @endcode
11 
12 Or through your package manager. Example:
13 
14 @code{.sh}
15 sudo apt-get install python-pyserial
16 @endcode
17 
18 Once you have the library installed, you must have your \b PYTHONPATH variable set
19 to the location of the library.
20 
21 @code{.sh}
22 export PYTHONPATH=/usr/lib/python2.7/site-packages
23 @endcode
24 
25 Replace 2.7 with your version of python (type in /usr/lib/python and press tab
26 once or twice).
27 
28 This may vary from system to system.
29 See the <a href="http://www.python.org/">python</a> and
30 <a href="http://pyserial.sourceforge.net/">pyserial</a> websites for help.
31 
32 To run the script, execute
33 
34 @code{.sh}
35 python -i servo_demo.py
36 @endcode
37 
38 or in python
39 
40 @code{.py}
41 import servo_demo
42 @endcode
43 
44 This will allow you to call the functions from a command interpreter.
45 It is recommended you experiment with all functions and view the source to
46 understand how you might produce commands. Also try demo() while connected.
47 
48 The movements included are quickly written and for demo purposes only.
49 
50 @see @ref index for the servo codes.
51 
52 @var ser
53 The serial connection for input and output. Must be initialized.
54 @see connect()
55 @var RHAND_ID
56 The identification byte of the right hand board.
57 """
58 
59 import serial
60 from time import sleep
61 
62 # Serial signals and responses
63 CANCEL_SIGNAL = 255
64 DUMP_SIGNAL = 253
65 START_RESPONSE = 252
66 END_RESPONSE = 251
67 RHAND_ID = 181
68 
69 global ser
70 ser = serial.Serial ()
71 
72 def connect (port):
73  """! Sets the global @ref ser variable.
74 
75  Call this to connect to the board.
76  @code{.py}
77  connect ('/dev/ttyUSB0')
78  @endcode
79  Replace \b /dev/ttyUSB0 with the name of the port your board is connected
80  to. Usually this is \b /dev/ttyUSB* for the arduino nano or \b
81  /dev/ttyACM* for the arduino uno. This is the same port used in @ref
82  Installation.
83 
84  Note that you must be part of the dialout group in Linux. Enter this
85  command to add yourself to a group.
86 
87  @code{.sh}
88  usermod -a -G dialout $USER
89  @endcode
90 
91  You must logout to apply group changes. If you cannot logout, you can run
92  the script as root.
93 
94  @code{.sh}
95  sudo su
96  export PYTHONPATH=/usr/lib/python2.7/site-packages
97  python -i example/servo_demo.py
98  @endcode
99  """
100  global ser
101  ser = serial.Serial (port, 9600, timeout=1)
102 
103 def sweep (initial, servos, starts, ends, steps):
104  """! Produces complex command chains.
105 
106  The output returned from the function is formatted in such a way that the
107  commands can be transmitted to the serial port at variable speeds.
108 
109  The parameters must all be lists of the same length whose indexes
110  correspond with \b servos.
111 
112  @param initial An array of initial commands to append to.
113  @param servos The servos to control.
114  @param starts The current positions of the servos.
115  @param ends The desired end positions.
116  @param steps The values to use to decrement/increment.
117  Converted to absolute value.
118 
119  @returns Upon success, returns the generated list of commands.
120  The commands are surrounded by the @ref CANCEL_SIGNAL for syncing.
121  The servos will always reach their exact final destination, unless if
122  \b steps at the index of the servo is 0.
123  Upon error, raises <b>'Invalid sweep command'</b>.
124  This indicates non-matching list lengths.
125 
126  @par Example
127  @code sweep ([], [1,2], [180,0], [90,180], [90,45]) @endcode
128  Returns
129  @code
130  [CANCEL_SIGNAL, 1, 180, 2, 0, 1, 90, 2, 45, 2, 90, 2, 135, 2, 180, CANCEL_SIGNAL]
131  @endcode
132  The protocol this follows is described in servo.ino.
133  By intertwining commands in this way, a delay can be inserted between
134  bytes and fluid motion is still preserved.
135  """
136 
137  # current angles of the servos
138  pos = list (starts)
139 
140  # length of the servo list
141  ln = len (servos)
142 
143  if ln != len (starts) or ln != len (ends) or ln != len (steps):
144  raise NameError ('Invalid sweep command')
145 
146  initial.append (CANCEL_SIGNAL)
147 
148  # the done list indicates which servos have completed their gestures
149  done = [0] * ln
150  while 1:
151  i = 0
152  while i < ln:
153  if done [i]:
154  i += 1
155  continue
156 
157  print ('servo:')
158  print (servos [i])
159  print ('pos:')
160  print (pos [i])
161  initial.append (servos [i])
162  initial.append (pos [i])
163 
164  # servo has reached its final position, indicate completion
165  if pos [i] == ends [i] or steps [i] == 0:
166  done [i] = 1
167  # the starting position preceeds the ending position, increment
168  elif starts [i] < ends [i]:
169  pos [i] += abs (steps [i])
170  # ensure that the servo reaches its exact destination
171  if pos [i] > ends [i]:
172  pos [i] = ends [i]
173  # the ending position preceeds the starting position, decrement
174  else:
175  pos [i] -= abs (steps [i])
176  # ensure that the servo reaches its exact destination
177  if pos [i] < ends [i]:
178  pos [i] = ends [i]
179  i += 1
180 
181  if not (0 in done):
182  break
183 
184  initial.append (CANCEL_SIGNAL)
185  return initial
186 
187 def unsweep (servos, ends, steps):
188  """! Produces a command chain that resets all servos to 0.
189  @see sweep()
190  """
191  starts = list (ends)
192  ends = list ([0 for x in servos])
193  return sweep ([], servos, starts, ends, steps)
194 
195 def servowrite (commands, delay):
196  """! Writes commands to the serial port.
197 
198  If \b delay is 0, immediately writes a list of ints to @ref ser as bytes.
199  Otherwise, writes a list of ints to @ref ser as bytes one at a time.
200 
201  Inserting a delay controls the speed of the movements.
202 
203  @see connect()
204  """
205  if delay == 0:
206  ser.write (commands)
207  else:
208  for n in commands:
209  ser.write ([n])
210  sleep (delay)
211 
212 def gesture (initial, servos, starts, ends, steps, delay):
213  """!
214  Sets the servos to a position, waits for input, then resets them to 0.
215  @see sweep() servowrite()
216  """
217  initial = sweep (initial, servos, starts, ends, steps)
218  print (initial)
219  servowrite (initial, delay)
220  input ()
221  initial = unsweep (servos, ends, steps)
222  print (initial)
223  servowrite (initial, delay)
224 
225 def reset ():
226  """! Immediately sets the servos to 0."""
227  servowrite ([CANCEL_SIGNAL,1,0,2,0,3,0,4,0,5,0], 0)
228 
229 def cmd ():
230  """! Sends the input to the serial port until a blank line is given.
231  Must enter integers between 0-255.
232 
233  @see @ref Arduino
234  """
235  while 1:
236  s = (input ())
237  if s == '':
238  break
239  servowrite ([int (s)], 0)
240 
241 def dump (servos=6):
242  """! Retrieves information about the board.
243  The command sends the @ref DUMP_SIGNAL to the serial port and reads back
244  the response. The response is described in servo.ino.
245 
246  When you write your own similar function, timing is important.
247 
248  We may need to increase the response delay on the Arduino if you are unable
249  to recieve a response. View the source of this function to understand how
250  it works, but keep in mind it is untested. Also keep in mind that the
251  function is very non-general as it always reads the same amount of bytes
252  instead of looking for the end signal.
253 
254  @note While this function could be used to change dynamically between
255  gestures instead of resetting the servos to 0, this function did not exist
256  when this script was first written.
257  """
258 
259  # ensure there is no pending serial input
260  ser.flushInput ()
261 
262  # indicate to the board that we with to retrieve information
263  servowrite ([DUMP_SIGNAL], 0)
264 
265  # read 10 bytes- the begin byte, the id of the board, the number of servos,
266  # the positions of the servos (6 servos on the hand), and the end byte
267  response = ser.read (servos * 2 + 4)
268  # convert the list of bytes into a list of ints
269  response = list (response)
270 
271  # terminate if the start or end byte is not correct
272  if not (response [0] == START_RESPONSE and response [-1] == END_RESPONSE):
273  return
274 
275  print ('id: ' + str (response [1]))
276  if response [1] == RHAND_ID:
277  print ('this board is the right hand.')
278  print ('servos: ' + str (response [2]))
279  servos = response [2]
280  i = 0
281  while i < servos * 2:
282  print ('servo ' + str (response [i + 3]) + ': ' + str (response [i + 4]))
283  i += 2
284 
285 # movements and gestures
286 
287 def peace (delay=0):
288  """! Makes a peace sign. @see gesture()"""
289  gesture ([CANCEL_SIGNAL,2,0,3,0], [1,4,5], [0] * 3, [180] * 3, [10] * 3, delay)
290 
291 def ok (delay=0.01):
292  """! Makes an ok sign. @see gesture()"""
293  gesture ([CANCEL_SIGNAL,3,0,4,0,5,0], [1,2], [0,0], [180,160], [10,10], delay)
294 
295 def grab (delay=0.02):
296  """! Makes a fist. @see gesture()"""
297  gesture ([], [1,2,3,4,5], [0] * 5, [180] * 5, [60] * 5, delay)
298 
299 def rockon (delay=0.01):
300  """! Makes a rock on sign. @see gesture()"""
301  gesture ([], [3,4], [0,0], [180,180], [10,10], 0.01)
302 
303 def wiggle (n=90, delay=0.01, wiggles=1):
304  """! Wiggles the fingers. @see sweep()"""
305  th = 40
306  step = n
307  servowrite (sweep ([CANCEL_SIGNAL,4,0,3,0,2,0,1,th], [5], [0], [n], [step]), delay)
308  while wiggles > 0:
309  servowrite (sweep ([], [5,4], [n,0], [0,n], [step,step]), delay)
310  servowrite (sweep ([], [4,3], [n,0], [0,n], [step,step]), delay)
311  servowrite (sweep ([], [3,2], [n,0], [0,n], [step,step]), delay)
312  servowrite (sweep ([], [2,1], [n,th], [0,n+th], [step] * 2), delay)
313  servowrite (sweep ([], [1], [n+th], [0], [step]), delay)
314  servowrite (sweep ([], [2,1], [0,th+n], [n,th], [step] * 2), delay)
315  servowrite (sweep ([], [3,2], [0,n], [n,0], [step,step]), delay)
316  servowrite (sweep ([], [4,3], [0,n], [n,0], [step,step]), delay)
317  servowrite (sweep ([], [5,4], [0,n], [n,0], [step,step]), delay)
318  wiggles -= 1
319  servowrite (sweep ([], [5], [n], [0], [step]), delay)
320 
321 def count ():
322  """! Counts to 5 on the fingers. @see sweep()"""
323  servowrite (sweep ([CANCEL_SIGNAL,2,0], [1,3,4,5], [0] * 4, [180] * 4, [10] * 4), 0.005)
324  servowrite (sweep ([], [3], [180], [0], [10]), 0.02)
325  servowrite (sweep ([], [4], [180], [0], [10]), 0.02)
326  servowrite (sweep ([], [5], [180], [0], [10]), 0.02)
327  servowrite (sweep ([], [1], [180], [0], [10]), 0.02)
328 
329 def demo ():
330  """! Performs all movements."""
331  peace ()
332  sleep (1)
333  ok ()
334  sleep (1)
335  grab ()
336  sleep (1)
337  wiggle ()
338  sleep (1)
339  count ()
340  sleep (1)
341  rockon ()
342 
343 def getarrow ():
344  import termios
345  import sys, tty
346  def _getch():
347  fd = sys.stdin.fileno()
348  old_settings = termios.tcgetattr(fd)
349  try:
350  tty.setraw(fd)
351  ch = sys.stdin.read(1)
352  finally:
353  termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
354  return ch
355 
356  c = _getch ()
357  if c != '\x1b':
358  return ''
359  c += _getch ()
360  if c != '\x1b[':
361  return ''
362  c += _getch ()
363 
364  if c == '\x1b[A':
365  return 'up'
366  elif c == '\x1b[B':
367  return 'down'
368  elif c == '\x1b[C':
369  return 'right'
370  elif c == '\x1b[D':
371  return 'left'
372  else:
373  return ''
374 
375 def calibrate (s=1):
376  """! Calibrate a servo.
377  Takes one argument, which is the id of a server. @ref index has the servo
378  codes.
379 
380  Use the left and right arrow keys to adjust the value of the servo. Use any
381  other key to exit.
382 
383  Enter the maximum values into the @ref limit array in your settings file.
384 
385  Note that if a servo isn't moving the correct way, for example if a finger
386  isn't flat at 0 and curved at 180, you must set @ref reverse to 1 at the
387  servo index.
388  """
389  i = 90
390  while 1:
391  print (i)
392  ser.write ([255, s, i])
393  arrow = getarrow ()
394  if arrow == 'up' or arrow == 'right':
395  if i < 180:
396  i += 5
397  elif arrow == 'down' or arrow == 'left':
398  if i > 0:
399  i -= 5
400  else:
401  break
402 
403 def calibrate_all ():
404  ser.write ([255])
405  for i in range (1, 6):
406  ser.write ([0])
407  ser.write ([0])
408  calibrate (i)
409  ser.write ([0])
410  ser.write ([180])
411  calibrate (i)
def connect(port)
Sets the global ser variable.
Definition: servo_demo.py:72
def reset()
Immediately sets the servos to 0.
Definition: servo_demo.py:225
def demo()
Performs all movements.
Definition: servo_demo.py:329
def ok(delay=0.01)
Makes an ok sign.
Definition: servo_demo.py:291
def unsweep(servos, ends, steps)
Produces a command chain that resets all servos to 0.
Definition: servo_demo.py:187
def wiggle(n=90, delay=0.01, wiggles=1)
Wiggles the fingers.
Definition: servo_demo.py:303
def rockon(delay=0.01)
Makes a rock on sign.
Definition: servo_demo.py:299
def peace(delay=0)
Makes a peace sign.
Definition: servo_demo.py:287
def cmd()
Sends the input to the serial port until a blank line is given.
Definition: servo_demo.py:229
def grab(delay=0.02)
Makes a fist.
Definition: servo_demo.py:295
def gesture(initial, servos, starts, ends, steps, delay)
Sets the servos to a position, waits for input, then resets them to 0.
Definition: servo_demo.py:212
def count()
Counts to 5 on the fingers.
Definition: servo_demo.py:321
def dump(servos=6)
Retrieves information about the board.
Definition: servo_demo.py:241
def servowrite(commands, delay)
Writes commands to the serial port.
Definition: servo_demo.py:195
def calibrate(s=1)
Calibrate a servo.
Definition: servo_demo.py:375
def sweep(initial, servos, starts, ends, steps)
Produces complex command chains.
Definition: servo_demo.py:103