async def _request(
            self,
            method: str,
            url: str,
            *,
            access_token: str = None,
            access_token_expiration: datetime = None,
            headers: dict = None,
            params: dict = None,
            json: dict = None,
            ssl: bool = True) -> dict:
        """Make a request against the RainMachine device."""
        if (access_token_expiration
                and datetime.now() >= access_token_expiration):
            raise TokenExpiredError('Long-lived access token has expired')

        if not headers:
            headers = {}
        headers.update({'Content-Type': 'application/json'})

        if not params:
            params = {}
        if access_token:
            params.update({'access_token': access_token})

        try:
            async with async_timeout.timeout(self.request_timeout):
                async with self._websession.request(
                        method, url, headers=headers, params=params, json=json,
                        ssl=ssl) as resp:
                    resp.raise_for_status()
                    data = await resp.json(content_type=None)
                    _raise_for_remote_status(url, data)
        except ClientError as err:
            raise RequestError(
                'Error requesting data from {0}: {1}'.format(url, err))
        except asyncio.TimeoutError:
            raise RequestError('Timeout during request: {0}'.format(url))

        return data