def connect(self, **options):
        """Connect with a Target or Initiator

        The calling thread is blocked until a single activation and
        deactivation has completed or a callback function supplied as
        the keyword argument ``terminate`` returns a true value. The
        example below makes :meth:`~connect()` return after 5 seconds,
        regardless of whether a peer device was connected or not.

        >>> import nfc, time
        >>> clf = nfc.ContactlessFrontend('usb')
        >>> after5s = lambda: time.time() - started > 5
        >>> started = time.time(); clf.connect(llcp={}, terminate=after5s)

        Connect options are given as keyword arguments with dictionary
        values. Possible options are:

        * ``rdwr={key: value, ...}`` - options for reader/writer
        * ``llcp={key: value, ...}`` - options for peer to peer
        * ``card={key: value, ...}`` - options for card emulation

        **Reader/Writer Options**

        'targets' : iterable
           A list of bitrate and technology type strings that will
           produce the :class:`~nfc.clf.RemoteTarget` objects to
           discover. The default is ``('106A', '106B', '212F')``.

        'on-startup' : function(targets)
           This function is called before any attempt to discover a
           remote card. The *targets* argument provides a list of
           :class:`RemoteTarget` objects prepared from the 'targets'
           bitrate and technology type strings. The function must
           return a list of of those :class:`RemoteTarget` objects
           that shall be finally used for discovery, those targets may
           have additional attributes. An empty list or anything else
           that evaluates false will remove the 'rdwr' option
           completely.

        'on-discover' : function(target)
           This function is called when a :class:`RemoteTarget` has
           been discovered. The *target* argument contains the
           technology type specific discovery responses and should be
           evaluated for multi-protocol support. The target will be
           further activated only if this function returns a true
           value. The default function depends on the 'llcp' option,
           if present then the function returns True only if the
           target does not indicate peer to peer protocol support,
           otherwise it returns True for all targets.

        'on-connect' : function(tag)
           This function is called when a remote tag has been
           activated. The *tag* argument is an instance of class
           :class:`nfc.tag.Tag` and can be used for tag reading and
           writing within the callback or in a separate thread. Any
           true return value instructs :meth:`connect` to wait until
           the tag is no longer present and then return True, any
           false return value implies immediate return of the
           :class:`nfc.tag.Tag` object.

        'on-release' : function(tag)
           This function is called when the presence check was run
           (the 'on-connect' function returned a true value) and
           determined that communication with the *tag* has become
           impossible, or when the 'terminate' function returned a
           true value. The *tag* object may be used for cleanup
           actions but not for communication.

        'iterations' : integer
           This determines the number of sense cycles performed
           between calls to the terminate function. Each iteration
           searches once for all specified targets. The default value
           is 5 iterations and between each iteration is a waiting
           time determined by the 'interval' option described below.
           As an effect of math there will be no waiting time if
           iterations is set to 1.

        'interval' : float
           This determines the waiting time between iterations. The
           default value of 0.5 seconds is considered a sensible
           tradeoff between responsiveness in terms of tag discovery
           and power consumption. It should be clear that changing
           this value will impair one or the other. There is no free
           beer.

        'beep-on-connect': boolean
            If the device supports beeping or flashing an LED,
            automatically perform this functionality when a tag is
            successfully detected AND the 'on-connect' function
            returns a true value. Defaults to True.

        .. sourcecode:: python

           import nfc

           def on_startup(targets):
               for target in targets:
                   target.sensf_req = bytearray.fromhex("0012FC0000")
               return targets

           def on_connect(tag):
               print(tag)

           rdwr_options = {
               'targets': ['212F', '424F'],
               'on-startup': on_startup,
               'on-connect': on_connect,
           }
           with nfc.ContactlessFrontend('usb') as clf:
               tag = clf.connect(rdwr=rdwr_options)
               if tag.ndef:
                   print(tag.ndef.message.pretty())

        **Peer To Peer Options**

        'on-startup' : function(llc)
           This function is called before any attempt to establish
           peer to peer communication. The *llc* argument provides the
           :class:`~nfc.llcp.llc.LogicalLinkController` that may be
           used to allocate and bind listen sockets for local
           services. The function should return the *llc* object if
           activation shall continue. Any other value removes the
           'llcp' option.

        'on-connect' : function(llc)
           This function is called when peer to peer communication is
           successfully established. The *llc* argument provides the
           now activated :class:`~nfc.llcp.llc.LogicalLinkController`
           ready for allocation of client communication sockets and
           data exchange in separate work threads. The function should
           a true value return more or less immediately, unless it
           wishes to handle the logical link controller run loop by
           itself and anytime later return a false value.

        'on-release' : function(llc)
           This function is called when the symmetry loop was run (the
           'on-connect' function returned a true value) and determined
           that communication with the remote peer has become
           impossible, or when the 'terminate' function returned a
           true value. The *llc* object may be used for cleanup
           actions but not for communication.

        'role' : string
           This attribute determines whether the local device will
           restrict itself to either ``'initiator'`` or ``'target'``
           mode of operation. As Initiator the local device will try
           to discover a remote device. As Target it waits for being
           discovered. The default is to alternate between both roles.

        'miu' : integer
           This attribute sets the maximum information unit size that
           is announced to the remote device during link activation.
           The default and also smallest possible value is 128 bytes.

        'lto' : integer
           This attribute sets the link timeout value (given in
           milliseconds) that is announced to the remote device during
           link activation. It informs the remote device that if the
           local device does not return a protocol data unit before
           the timeout expires, the communication link is broken and
           can not be recovered. The *lto* is an important part of the
           user experience, it ultimately tells when the user should
           no longer expect communication to continue. The default
           value is 500 millisecond.

        'agf' : boolean
           Some early phone implementations did not properly handle
           aggregated protocol data units. This attribute allows to
           disable the use af aggregation at the cost of efficiency.
           Aggregation is disabled with a false value. The default
           is to use aggregation.

        'brs' : integer
           For the local device in Initiator role the bit rate
           selector determines the the bitrate to negotiate with the
           remote Target. The value may be 0, 1, or 2 for 106, 212, or
           424 kbps, respectively. The default is to negotiate 424
           kbps.

        'acm' : boolean
           For the local device in Initiator role this attribute
           determines whether a remote Target may also be activated in
           active communication mode. In active communication mode
           both peer devices mutually generate a radio field when
           sending. The default is to use passive communication mode.

        'rwt' : float
           For the local device in Target role this attribute sets the
           response waiting time announced during link activation. The
           response waiting time is a medium access layer (NFC-DEP)
           value that indicates when the remote Initiator shall
           attempt error recovery after missing a Target response. The
           value is the waiting time index *wt* that determines the
           effective response waiting time by the formula ``rwt =
           4096/13.56E6 * pow(2, wt)``. The value shall not be greater
           than 14. The default value is 8 and yields an effective
           response waiting time of 77.33 ms.

        'lri' : integer
           For the local device in Initiator role this attribute sets
           the length reduction for medium access layer (NFC-DEP)
           information frames. The value may be 0, 1, 2, or 3 for a
           maximum payload size of 64, 128, 192, or 254 bytes,
           respectively. The default value is 3.

        'lrt' : integer
           For the local device in Target role this attribute sets
           the length reduction for medium access layer (NFC-DEP)
           information frames. The value may be 0, 1, 2, or 3 for a
           maximum payload size of 64, 128, 192, or 254 bytes,
           respectively. The default value is 3.

        .. sourcecode:: python

           import nfc
           import nfc.llcp
           import threading

           def server(socket):
               message, address = socket.recvfrom()
               socket.sendto("It's me!", address)
               socket.close()

           def client(socket):
               socket.sendto("Hi there!", address=32)
               socket.close()

           def on_startup(llc):
               socket = nfc.llcp.Socket(llc, nfc.llcp.LOGICAL_DATA_LINK)
               socket.bind(address=32)
               threading.Thread(target=server, args=(socket,)).start()
               return llc

           def on_connect(llc):
               socket = nfc.llcp.Socket(llc, nfc.llcp.LOGICAL_DATA_LINK)
               threading.Thread(target=client, args=(socket,)).start()
               return True

           llcp_options = {
               'on-startup': on_startup,
               'on-connect': on_connect,
           }
           with nfc.ContactlessFrontend('usb') as clf:
               clf.connect(llcp=llcp_options)
               print("link terminated")

        **Card Emulation Options**

        'on-startup' : function(target)
           This function is called to prepare a local target for
           discovery. The input argument is a fresh instance of an
           unspecific :class:`LocalTarget` that can be set to the
           desired bitrate and modulation type and populated with the
           type specific discovery responses (see :meth:`listen` for
           response data that is needed). The fully specified target
           object must then be returned.

        'on-discover' : function(target)
           This function is called when the :class:`LocalTarget` has
           been discovered. The *target* argument contains the
           technology type specific discovery commands. The target
           will be further activated only if this function returns a
           true value. The default function always returns True.

        'on-connect' : function(tag)
           This function is called when the local target was
           discovered and a :class:`nfc.tag.TagEmulation` object
           successfully initialized. The function receives the
           emulated *tag* object which stores the first command
           received after inialization as ``tag.cmd``. The function
           should return a true value if the tag.process_command() and
           tag.send_response() methods shall be called repeatedly
           until either the remote device terminates communication or
           the 'terminate' function returns a true value. The function
           should return a false value if the :meth:`connect` method
           shall return immediately with the emulated *tag* object.

        'on-release' : function(tag)
           This function is called when the Target was released by the
           Initiator or simply moved away, or if the terminate
           callback function has returned a true value. The emulated
           *tag* object may be used for cleanup actions but not for
           communication.

        .. sourcecode:: python

           import nfc

           def on_startup(target):
               idm = bytearray.fromhex("01010501b00ac30b")
               pmm = bytearray.fromhex("03014b024f4993ff")
               sys = bytearray.fromhex("1234")
               target.brty = "212F"
               target.sensf_res = chr(1) + idm + pmm + sys
               return target

           def on_connect(tag):
               print("discovered by remote reader")
               return True

           def on_release(tag):
               print("remote reader is gone")
               return True

           card_options = {
               'on-startup': on_startup,
               'on-connect': on_connect,
               'on-release': on_release,
           }
           with nfc.ContactlessFrontend('usb') as clf:
               clf.connect(card=card_options)

        **Return Value**

        The :meth:`connect` method returns :const:`None` if there were
        no options left after the 'on-startup' functions have been
        executed or when the 'terminate' function returned a true
        value. It returns :const:`False` when terminated by any of the
        following exceptions: :exc:`~exceptions.KeyboardInterrupt`,
        :exc:`~exceptions.IOError`, :exc:`UnsupportedTargetError`.

        The :meth:`connect` method returns a :class:`~nfc.tag.Tag`,
        :class:`~nfc.llcp.llc.LogicalLinkController`, or
        :class:`~nfc.tag.TagEmulation` object if the associated
        'on-connect' function returned a false value to indicate that
        it will handle presence check, peer to peer symmetry loop, or
        command/response processing by itself.

        """
        if self.device is None:
            raise IOError(errno.ENODEV, os.strerror(errno.ENODEV))

        log.debug("connect{0}".format(
            tuple([k for k in options if options[k]])))

        terminate = options.get('terminate', lambda: False)
        rdwr_options = options.get('rdwr')
        llcp_options = options.get('llcp')
        card_options = options.get('card')

        try:
            assert isinstance(rdwr_options, (dict, type(None))), "rdwr"
            assert isinstance(llcp_options, (dict, type(None))), "llcp"
            assert isinstance(card_options, (dict, type(None))), "card"
        except AssertionError as error:
            raise TypeError("argument '%s' must be a dictionary" % error)

        if llcp_options is not None:
            llcp_options = dict(llcp_options)
            llcp_options.setdefault('on-startup', lambda llc: llc)
            llcp_options.setdefault('on-connect', lambda llc: True)
            llcp_options.setdefault('on-release', lambda llc: True)

            llc = nfc.llcp.llc.LogicalLinkController(**llcp_options)
            llc = llcp_options['on-startup'](llc)
            if isinstance(llc, nfc.llcp.llc.LogicalLinkController):
                llcp_options['llc'] = llc
            else:
                log.debug("removing llcp_options after on-startup")
                llcp_options = None

        if rdwr_options is not None:
            def on_discover(target):
                if target.sel_res and target.sel_res[0] & 0x40:
                    return False
                elif target.sensf_res and target.sensf_res[1:3] == b"\x01\xFE":
                    return False
                else:
                    return True

            rdwr_options = dict(rdwr_options)
            rdwr_options.setdefault('targets', ['106A', '106B', '212F'])
            rdwr_options.setdefault('on-startup', lambda targets: targets)
            rdwr_options.setdefault('on-discover', on_discover)
            rdwr_options.setdefault('on-connect', lambda tag: True)
            rdwr_options.setdefault('on-release', lambda tag: True)
            rdwr_options.setdefault('iterations', 5)
            rdwr_options.setdefault('interval', 0.5)
            rdwr_options.setdefault('beep-on-connect', True)

            targets = [RemoteTarget(brty) for brty in rdwr_options['targets']]
            targets = rdwr_options['on-startup'](targets)
            if targets and all([isinstance(o, RemoteTarget) for o in targets]):
                rdwr_options['targets'] = targets
            else:
                log.debug("removing rdwr_options after on-startup")
                rdwr_options = None

        if card_options is not None:
            card_options = dict(card_options)
            card_options.setdefault('on-startup', lambda target: None)
            card_options.setdefault('on-discover', lambda target: True)
            card_options.setdefault('on-connect', lambda tag: True)
            card_options.setdefault('on-release', lambda tag: True)

            target = nfc.clf.LocalTarget()
            target = card_options['on-startup'](target)
            if isinstance(target, LocalTarget):
                card_options['target'] = target
            else:
                log.debug("removing card_options after on-startup")
                card_options = None

        if not (rdwr_options or llcp_options or card_options):
            log.warning("no options to connect")
            return None

        log.debug("connect options after startup: %s",
                  ', '.join(filter(bool, ["rdwr" if rdwr_options else None,
                                          "llcp" if llcp_options else None,
                                          "card" if card_options else None])))

        try:
            while not terminate():
                if rdwr_options:
                    result = self._rdwr_connect(rdwr_options, terminate)
                    if bool(result) is True:
                        return result
                if llcp_options:
                    result = self._llcp_connect(llcp_options, terminate)
                    if bool(result) is True:
                        return result
                if card_options:
                    result = self._card_connect(card_options, terminate)
                    if bool(result) is True:
                        return result
        except IOError as error:
            log.error(error)
            return False
        except UnsupportedTargetError as error:
            log.info(error)
            return False
        except KeyboardInterrupt:
            log.debug("terminated by keyboard interrupt")
            return False