HTML

finchconnection.py

Nov 18th, 2016
255
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.78 KB | None | 0 0
  1. # This module implements connection of a Finch robot via USB. It is used by
  2. # finch.py to send and receive commands from the Finch.
  3. # The Finch is a robot for computer science education. Its design is the result
  4. # of a four year study at Carnegie Mellon's CREATE lab.
  5. # http://www.finchrobot.com
  6.  
  7. import atexit
  8. import os
  9. import ctypes
  10. import threading
  11. import time
  12. import platform
  13. import sys
  14.  
  15. VENDOR_ID = 0x2354
  16. DEVICE_ID = 0x1111
  17.  
  18. HIDAPI_LIBRARY_PATH = os.environ.get('HIDAPI_LIB_PATH', './')
  19. PING_FREQUENCY_SECONDS = 2.0 # seconds
  20.  
  21. # Detect which operating system is present and load corresponding library
  22.  
  23. system = platform.system()
  24.  
  25. if system == 'Windows':
  26.     if sys.maxsize > 2**32:
  27.         hid_api = ctypes.CDLL(os.path.join(HIDAPI_LIBRARY_PATH, "hidapi64.dll"))
  28.     else:
  29.         hid_api = ctypes.CDLL(os.path.join(HIDAPI_LIBRARY_PATH, "hidapi32.dll"))
  30.        
  31. elif system == 'Linux':
  32.     if sys.maxsize > 2**32:
  33.         hid_api = ctypes.CDLL(os.path.join(HIDAPI_LIBRARY_PATH, "libhidapi64.so"))
  34.     else:
  35.         hid_api = ctypes.CDLL(os.path.join(HIDAPI_LIBRARY_PATH, "libhidapi32.so"
  36.                                            ))
  37. elif system == 'Darwin':
  38.     hid_api = ctypes.CDLL(os.path.join(HIDAPI_LIBRARY_PATH, "libhidapi.dylib"))
  39. else:
  40.     hid_api = ctypes.CDLL(os.path.join(HIDAPI_LIBRARY_PATH, "libhidapipi.so"))
  41.    
  42. def _inherit_docstring(cls):
  43.     def doc_setter(method):
  44.         parent = getattr(cls, method.__name__)
  45.         method.__doc__ = parent.__doc__
  46.         return method
  47.     return doc_setter
  48.  
  49. class FinchConnection:
  50.     """ USB connection to the Finch robot. Uses the HID API
  51.        to read and write from the robot. """
  52.  
  53.     c_finch_handle = ctypes.c_void_p(None)
  54.     c_io_buffer = ctypes.c_char_p(None)
  55.     cmd_id = 0
  56.  
  57.     def is_open(self):
  58.         """Returns True if connected to the robot."""
  59.         return bool(self.c_finch_handle)
  60.  
  61.     def open(self):
  62.         """ Connect to the robot.
  63.  
  64.        This method looks for a USB port the Finch is connceted to. """
  65.        
  66.         _before_new_finch_connection(self)
  67.         if self.is_open():
  68.             self.close()
  69.         try:
  70.             hid_api.hid_open.restype = ctypes.c_void_p
  71.             self.c_finch_handle = hid_api.hid_open(
  72.                 ctypes.c_ushort(VENDOR_ID),
  73.                 ctypes.c_ushort(DEVICE_ID),
  74.                 ctypes.c_void_p(None))
  75.             self.c_io_buffer = ctypes.create_string_buffer(9)
  76.             _new_finch_connected(self)
  77.             self.cmd_id = self.read_cmd_id()
  78.         except:
  79.             raise Exception("Failed to connect to the Finch robot.")
  80.  
  81.     def close(self):
  82.         """ Disconnect the robot. """
  83.        
  84.         if self.c_finch_handle:
  85.             self.send(b'R', [0]) # exit to idle (rest) mode
  86.             hid_api.hid_close.argtypes = [ctypes.c_void_p]
  87.             hid_api.hid_close(self.c_finch_handle)
  88.         self.c_finch_handle = ctypes.c_void_p(None)
  89.         self.c_io_buffer = ctypes.c_char_p(None)
  90.        
  91.         global _open_finches
  92.         if self in _open_finches:
  93.             _open_finches.remove(self)
  94.  
  95.     def read_cmd_id(self):
  96.         """ Read the robot's internal command counter. """
  97.        
  98.         #self.send('z', receive = True)
  99.         self.send(b'z')
  100.         data = self.receive()
  101.         return data[0]
  102.  
  103.     def send(self, command, payload=()):
  104.         """Send a command to the robot (internal).
  105.  
  106.        command: The command ASCII character
  107.        payload: a list of up to 6 bytes of additional command info
  108.        """
  109.        
  110.         if not self.is_open():
  111.             raise Exception("Connection to Finch was closed.")
  112.        
  113.         # Format the buffer to contain the contents of the payload.
  114.         for i in range(7):
  115.             self.c_io_buffer[i] = b'\x00'
  116.         self.c_io_buffer[1] = command[0]
  117.  
  118.         python_version = sys.version_info[0]
  119.  
  120.         if payload:
  121.             for i in range(len(payload)):
  122.                 if python_version >= 3:
  123.                     self.c_io_buffer[i+2] = payload[i]
  124.                 else:
  125.                     self.c_io_buffer[i+2] = bytes(chr(payload[i]))
  126.         # Make sure command id is incremented if this is a receive case
  127.         else:
  128.             self.cmd_id = (self.cmd_id + 1) % 256
  129.  
  130.         # Make sure the command id is incremented so that two calls to obstacle() don't cause the system to hang
  131.         #self.cmd_id = (self.cmd_id + 1) % 256
  132.         #if self.cmd_id == 0:
  133.         #    self.cmd_id = 1
  134.                
  135.         if python_version >= 3:
  136.             self.c_io_buffer[8] = self.cmd_id
  137.         else:
  138.             self.c_io_buffer[8] = bytes(chr(self.cmd_id))
  139.        
  140.         # Write to the Finch bufffer
  141.         res = 0
  142.         while not res:
  143.             hid_api.hid_write.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t]
  144.             res = hid_api.hid_write(self.c_finch_handle,
  145.                                     self.c_io_buffer,
  146.                                     ctypes.c_size_t(9))
  147.        
  148.     def receive(self):
  149.         """ Read the data from the Finch buffer. """
  150.        
  151.         res = 9
  152.         while res > 0:
  153.             hid_api.hid_read.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t]
  154.             if system == 'Darwin':
  155.                 res = hid_api.hid_read(self.c_finch_handle,
  156.                                     self.c_io_buffer,
  157.                                     ctypes.c_size_t(9))
  158.             else:
  159.                 res = hid_api.hid_read_timeout(self.c_finch_handle,
  160.                                     self.c_io_buffer,
  161.                                     ctypes.c_size_t(9),
  162.                                     50)
  163.             if self.cmd_id == ord(self.c_io_buffer[8]):
  164.                 break
  165.         return [ord(self.c_io_buffer[i]) for i in range(9)]
  166.    
  167. class ThreadedFinchConnection(FinchConnection):
  168.     """Threaded implementation of Finch Connection"""
  169.    
  170.     lock = None
  171.     thread = None
  172.     main_thread = None
  173.     last_cmd_sent = time.time()
  174.  
  175.     @_inherit_docstring(FinchConnection)
  176.     def open(self):
  177.         FinchConnection.open(self)
  178.         if not self.is_open():
  179.             return
  180.         self.lock = threading.Lock()
  181.         self.thread = threading.Thread(target=self.__class__._pinger, args=(self, ))
  182.         self.main_thread = threading.current_thread()
  183.         self.thread.start()
  184.  
  185.     @_inherit_docstring(FinchConnection)
  186.     def send(self, command, payload=(), receive=False):
  187.         try:
  188.             if self.lock is not None:
  189.                 self.lock.acquire()
  190.             #FinchConnection.send(self, command, payload=payload, receive=receive)
  191.             FinchConnection.send(self, command, payload=payload)
  192.             self.last_cmd_sent = time.time()
  193.         finally:
  194.             if self.lock is not None:
  195.                 self.lock.release()
  196.  
  197.     @_inherit_docstring(FinchConnection)
  198.     def receive(self):
  199.         try:
  200.             if self.lock is not None:
  201.                 self.lock.acquire()
  202.             data = FinchConnection.receive(self)
  203.         finally:
  204.             if self.lock is not None:
  205.                 self.lock.release()
  206.         return data
  207.    
  208.     def _pinger(self):
  209.         """ Sends keep-alive commands every few secconds of inactivity. """
  210.  
  211.         while True:
  212.             if not self.lock:
  213.                 break
  214.             if not self.c_finch_handle:
  215.                 break
  216.             if not self.main_thread.isAlive():
  217.                 break
  218.             try:
  219.                 self.lock.acquire()
  220.                 now = time.time()
  221.                 if self.last_cmd_sent:
  222.                     delta = now - self.last_cmd_sent
  223.                 else:
  224.                     delta = PING_FREQUENCY_SECONDS
  225.                 if delta >= PING_FREQUENCY_SECONDS:
  226.                     FinchConnection.send(self, b'z')
  227.                     FinchConnection.receive(self)
  228.                     self.last_cmd_sent = now
  229.             finally:
  230.                 self.lock.release()
  231.             time.sleep(0.1)
  232.  
  233.     @_inherit_docstring(FinchConnection)
  234.     def close(self):
  235.         FinchConnection.close(self)
  236.         self.thread.join()
  237.         self.lock = None
  238.         self.thread = None
  239.  
  240. # Functions that handle the list of open finches
  241.  
  242. _open_finches = []
  243.  
  244. def _before_new_finch_connection(finch):
  245.     global _open_finches
  246.     # close other connections
  247.     for robot in _open_finches:
  248.         if robot.is_open():
  249.             robot.close()
  250.  
  251.  
  252. def _new_finch_connected(finch):
  253.     global _open_finches
  254.     if finch not in _open_finches:
  255.         _open_finches.append(finch)
  256.  
  257.  
  258. def _close_all_finches():
  259.     global _open_finches
  260.     if not _open_finches:
  261.         return
  262.     for finch in _open_finches:
  263.         if finch.is_open():
  264.             finch.close()
  265.  
  266. atexit.register(_close_all_finches)
Advertisement
Comments
  • User was banned
Add Comment
Please, Sign In to add comment