docs: def connect(self, timeout=2):
          """Connect to the KNX/IP tunnelling interface.
  
          If the remote address is "0.0.0.0", it will use the Gateway scanner
          to automatically detect a KNX gateway and it will connect to it if one
          has been found.
  
          Returns true if a connection could be established, false otherwise
          """
  
          if self.connected:
              logging.info("KNXIPTunnel connect request ignored, "
                           "already connected")
              return True
  
          if self.remote_ip == "0.0.0.0":
              scanner = GatewayScanner()
              try:
                  ipaddr, port = scanner.start_search()
                  logging.info("Found KNX gateway %s/%s", ipaddr, port)
                  self.remote_ip = ipaddr
                  self.remote_port = port
              except TypeError:
                  logging.error("No KNX/IP gateway given and no gateway "
                                "found by scanner, aborting %s")
  
          # Clean up cache
          self.value_cache.clear()
  
          # Find my own IP
          sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
          sock.connect((self.remote_ip, self.remote_port))
          local_ip = sock.getsockname()[0]
  
          if self.data_server:
              logging.info("Data server already running, not starting again")
          else:
              self.data_server = DataServer((local_ip, 0),
                                            DataRequestHandler,
                                            self)
              dummy_ip, self.data_port = self.data_server.server_address
              data_server_thread = threading.Thread(
                  target=self.data_server.serve_forever)
              data_server_thread.daemon = True
              data_server_thread.start()
              logging.debug(
                  "Started data server on UDP port %s", self.data_port)
  
          self.control_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
          self.control_socket.bind((local_ip, 0))
          self.control_socket.settimeout(timeout)
  
          # Connect packet
          frame = KNXIPFrame(KNXIPFrame.CONNECT_REQUEST)
  
          # Control endpoint
          body = []
          body.extend([0x08, 0x01])  # length 8 bytes, UPD
          dummy_ip, port = self.control_socket.getsockname()
          body.extend(ip_to_array(local_ip))
          body.extend(int_to_array(port, 2))
  
          # Data endpoint
          body.extend([0x08, 0x01])  # length 8 bytes, UPD
          body.extend(ip_to_array(local_ip))
          body.extend(int_to_array(self.data_port, 2))
  
          #
          body.extend([0x04, 0x04, 0x02, 0x00])
          frame.body = body
  
          try:
              self.control_socket.sendto(bytes(frame.to_frame()),
                                         (self.remote_ip, self.remote_port))
              received = self.control_socket.recv(1024)
          except socket.error:
              self.control_socket.close()
              self.control_socket = None
              logging.error("KNX/IP gateway did not respond to connect request")
              return False
  
          # Check if the response is an TUNNELING ACK
          r_sid = received[2] * 256 + received[3]
  
          if r_sid == KNXIPFrame.CONNECT_RESPONSE:
              self.channel = received[6]
              status = received[7]
              if status == 0:
                  hpai = received[8:10]
                  logging.debug("Connected KNX IP tunnel " +
                                "(Channel: {}, HPAI: {} {})".format(
                                    self.channel, hpai[0], hpai[1]))
              else:
                  logging.error("KNX IP tunnel connect error:" +
                                "(Channel: {}, Status: {})".format(
                                    self.channel, status))
                  return False
  
          else:
              logging.error(
                  "Could not initiate tunnel connection, STI = {0:%s}", r_sid)
              return False
  
          self.connected = True
  
          return True