docs: def bind_controllers(*discoveries, print_events=False):
      """
      Bind a controller or controllers to a set of evdev InputDevice instances, starting a thread to keep those
      controllers in sync with the state of the hardware.
      
      :param discoveries:
          ControllerDiscovery instances specifying the controllers and their associated input devices
      :param print_events:
          Defaults to False, if set to True then all events picked up by this binder will be printed to stdout
      :return: 
          A function which can be used to stop the event reading thread and unbind from the device
      """
  
      discoveries = list(discoveries)
  
      class SelectThread(Thread):
          def __init__(self):
              Thread.__init__(self, name='evdev select thread')
              self.daemon = True
              self.running = True
  
              self.device_to_controller_discovery = {}
              for discovery in discoveries:
                  for d in discovery.devices:
                      self.device_to_controller_discovery[d.fn] = discovery
              self.all_devices = reduce(lambda x, y: x + y, [discovery.devices for discovery in discoveries])
  
          def run(self):
  
              for discovery in discoveries:
                  discovery.controller.device_unique_name = discovery.name
  
              while self.running:
                  try:
                      r, w, x = select(self.all_devices, [], [], 0.5)
                      for fd in r:
                          active_device = fd
                          controller_discovery = self.device_to_controller_discovery[active_device.fn]
                          controller = controller_discovery.controller
                          controller_devices = controller_discovery.devices
                          prefix = None
                          if controller.node_mappings is not None and len(controller_devices) > 1:
                              try:
                                  prefix = controller.node_mappings[active_device.name]
                              except KeyError:
                                  pass
                          for event in active_device.read():
                              if print_events:
                                  print(event)
                              if event.type == EV_ABS or event.type == EV_REL:
                                  controller.axes.axis_updated(event, prefix=prefix)
                              elif event.type == EV_KEY:
                                  # Button event
                                  if event.value == 1:
                                      # Button down
                                      controller.buttons.button_pressed(event.code, prefix=prefix)
                                  elif event.value == 0:
                                      # Button up
                                      controller.buttons.button_released(event.code, prefix=prefix)
                  except Exception as e:
                      self.stop(e)
  
          def stop(self, exception=None):
  
              for discovery in discoveries:
                  discovery.controller.device_unique_name = None
                  discovery.controller.exception = exception
  
              self.running = False
  
      polling_thread = SelectThread()
  
      # Force an update of the LED and battery system cache
      sys.scan_cache(force_update=True)
  
      for device in polling_thread.all_devices:
          device.grab()
  
      def unbind():
          polling_thread.stop()
          for dev in polling_thread.all_devices:
              try:
                  dev.ungrab()
              except IOError:
                  pass
  
      polling_thread.start()
  
      return unbind