public function soap(string $service, string $action, array $params = [])
    {
        switch ($service) {
            case "AVTransport":
            case "RenderingControl":
                $path = "MediaRenderer";
                break;
            case "ContentDirectory":
                $path = "MediaServer";
                break;
            case "AlarmClock":
            case "DeviceProperties":
            case "ZoneGroupTopology":
                $path = null;
                break;
            default:
                throw new \InvalidArgumentException("Unknown service: {$service}");
        }

        $location = "http://{$this->ip}:1400/";
        if (is_string($path)) {
            $location .= "{$path}/";
        }
        $location .= "{$service}/Control";

        $this->logger->info("sending soap request to: {$location}", $params);

        $soap = new \SoapClient(null, [
            "location"  =>  $location,
            "uri"       =>  "urn:schemas-upnp-org:service:{$service}:1",
            "trace"     =>  true,
        ]);

        $soapParams = [];
        $params["InstanceID"] = 0;
        foreach ($params as $key => $val) {
            $soapParams[] = new \SoapParam(new \SoapVar($val, \XSD_STRING), $key);
        }

        try {
            $result = $soap->__soapCall($action, $soapParams);
            $this->logger->debug("REQUEST: " . $soap->__getLastRequest());
            $this->logger->debug("RESPONSE: " . $soap->__getLastResponse());
        } catch (\SoapFault $e) {
            $this->logger->debug("REQUEST: " . $soap->__getLastRequest());
            $this->logger->debug("RESPONSE: " . $soap->__getLastResponse());
            throw new SoapException($e, $soap);
        }

        return $result;
    }