code: async def ping(self, data: Optional[bytes] = None) -> Awaitable[None]:
          
          await self.ensure_open()
  
          if data is not None:
              data = encode_data(data)
  
          # Protect against duplicates if a payload is explicitly set.
          if data in self.pings:
              raise ValueError("Already waiting for a pong with the same data")
  
          # Generate a unique random payload otherwise.
          while data is None or data in self.pings:
              data = struct.pack("!I", random.getrandbits(32))
  
          self.pings[data] = self.loop.create_future()
  
          await self.write_frame(True, OP_PING, data)
  
          return asyncio.shield(self.pings[data])
docs: This coroutine sends a ping.
  
          It returns a :class:`~asyncio.Future` which will be completed when the
          corresponding pong is received and which you may ignore if you don't
          want to wait.
  
          A ping may serve as a keepalive or as a check that the remote endpoint
          received all messages up to this point::
  
              pong_waiter = await ws.ping()
              await pong_waiter   # only if you want to wait for the pong
  
          By default, the ping contains four random bytes. The content may be
          overridden with the optional ``data`` argument which must be a string
          (which will be encoded to UTF-8) or a bytes-like object.