docs: def add_interrupt_callback(self, gpio_id, callback, edge='both',
              pull_up_down=_GPIO.PUD_OFF, threaded_callback=False,
              debounce_timeout_ms=None):
          """
          Add a callback to be executed when the value on 'gpio_id' changes to
          the edge specified via the 'edge' parameter (default='both').
  
          `pull_up_down` can be set to `RPIO.PUD_UP`, `RPIO.PUD_DOWN`, and
          `RPIO.PUD_OFF`.
  
          If `threaded_callback` is True, the callback will be started
          inside a Thread.
          """
          gpio_id = _GPIO.channel_to_gpio(gpio_id)
          debug("Adding callback for GPIO %s" % gpio_id)
          if not edge in ["falling", "rising", "both", "none"]:
              raise AttributeError("'%s' is not a valid edge." % edge)
  
          if not pull_up_down in [_GPIO.PUD_UP, _GPIO.PUD_DOWN, _GPIO.PUD_OFF]:
              raise AttributeError("'%s' is not a valid pull_up_down." % edge)
  
          # Make sure the gpio_id is valid
          if not gpio_id in set(chain(RPIO.GPIO_LIST_R1, RPIO.GPIO_LIST_R2, \
                                RPIO.GPIO_LIST_R3)):
              raise AttributeError("GPIO %s is not a valid gpio-id." % gpio_id)
  
          # Require INPUT pin setup; and set the correct PULL_UPDN
          if RPIO.gpio_function(int(gpio_id)) == RPIO.IN:
              RPIO.set_pullupdn(gpio_id, pull_up_down)
          else:
              debug("- changing gpio function from %s to INPUT" % \
                      (GPIO_FUNCTIONS[RPIO.gpio_function(int(gpio_id))]))
              RPIO.setup(gpio_id, RPIO.IN, pull_up_down)
  
          # Prepare the callback (wrap in Thread if needed)
          cb = callback if not threaded_callback else \
                  partial(_threaded_callback, callback)
  
          # Prepare the /sys/class path of this gpio
          path_gpio = "%sgpio%s/" % (_SYS_GPIO_ROOT, gpio_id)
  
          # If initial callback for this GPIO then set everything up. Else make
          # sure the edge detection is the same.
          if gpio_id in self._map_gpioid_to_callbacks:
              with open(path_gpio + "edge", "r") as f:
                  e = f.read().strip()
                  if e != edge:
                      raise AttributeError(("Cannot add callback for gpio %s:"
                              " edge detection '%s' not compatible with existing"
                              " edge detection '%s'.") % (gpio_id, edge, e))
  
              # Check whether edge is the same, else throw Exception
              debug("- kernel interface already setup for GPIO %s" % gpio_id)
              self._map_gpioid_to_callbacks[gpio_id].append(cb)
  
          else:
              # If kernel interface already exists unexport first for clean setup
              if os.path.exists(path_gpio):
                  if self._show_warnings:
                      warn("Kernel interface for GPIO %s already exists." % \
                              gpio_id)
                  debug("- unexporting kernel interface for GPIO %s" % gpio_id)
                  with open(_SYS_GPIO_ROOT + "unexport", "w") as f:
                      f.write("%s" % gpio_id)
                  time.sleep(0.1)
  
              # Export kernel interface /sys/class/gpio/gpioN
              with open(_SYS_GPIO_ROOT + "export", "w") as f:
                  f.write("%s" % gpio_id)
              self._gpio_kernel_interfaces_created.append(gpio_id)
              debug("- kernel interface exported for GPIO %s" % gpio_id)
  
              # Configure gpio as input
              with open(path_gpio + "direction", "w") as f:
                  f.write("in")
  
              # Configure gpio edge detection
              with open(path_gpio + "edge", "w") as f:
                  f.write(edge)
  
              debug(("- kernel interface configured for GPIO %s "
                      "(edge='%s', pullupdn=%s)") % (gpio_id, edge, \
                      _PULL_UPDN[pull_up_down]))
  
              # Open the gpio value stream and read the initial value
              f = open(path_gpio + "value", 'r')
              val_initial = f.read().strip()
              debug("- inital gpio value: %s" % val_initial)
              f.seek(0)
  
              # Add callback info to the mapping dictionaries
              self._map_fileno_to_file[f.fileno()] = f
              self._map_fileno_to_gpioid[f.fileno()] = gpio_id
              self._map_fileno_to_options[f.fileno()] = {
                      "debounce_timeout_s": debounce_timeout_ms / 1000.0 if \
                              debounce_timeout_ms else 0,
                      "interrupt_last": 0,
                      "edge": edge
                      }
              self._map_gpioid_to_fileno[gpio_id] = f.fileno()
              self._map_gpioid_to_callbacks[gpio_id] = [cb]
  
              # Add to epoll
              self._epoll.register(f.fileno(), select.EPOLLPRI | select.EPOLLERR)