<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 async def ping(self, data: Optional[bytes] = None) -> Awaitable[None]: """ 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. """

        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])