def call(self, action_name, arg_in=None, http_auth=None, http_headers=None):
        """
        Construct the XML and make the call to the device. Parse the response values into a dict.
        """
        if arg_in is None:
            arg_in = {}

        soap_env = '{%s}' % NS_SOAP_ENV
        m = '{%s}' % self.service_type

        root = etree.Element(soap_env+'Envelope', nsmap={'SOAP-ENV': NS_SOAP_ENV})
        root.attrib[soap_env+'encodingStyle'] = ENCODING_STYLE
        body = etree.SubElement(root, soap_env+'Body')
        action = etree.SubElement(body, m+action_name, nsmap={'m': self.service_type})
        for key, value in arg_in.items():
            etree.SubElement(action, key).text = str(value)
        body = etree.tostring(root, encoding=ENCODING, xml_declaration=True)
        headers = {
            'SOAPAction': '"%s#%s"' % (self.service_type, action_name),
            'Host': self._host,
            'Content-Type': 'text/xml',
            'Content-Length': str(len(body)),
        }
        headers.update(http_headers or {})

        try:
            resp = requests.post(
                self.url,
                body,
                headers=headers,
                timeout=SOAP_TIMEOUT,
                auth=http_auth
            )
            resp.raise_for_status()
        except requests.exceptions.HTTPError as exc:
            # If the body of the error response contains XML then it should be a UPnP error,
            # otherwise reraise the HTTPError.
            try:
                err_xml = etree.fromstring(exc.response.content)
            except etree.XMLSyntaxError:
                raise exc
            raise SOAPError(*self._extract_upnperror(err_xml))

        xml_str = resp.content.strip()
        try:
            xml = etree.fromstring(xml_str)
        except etree.XMLSyntaxError:
            # Try removing any extra XML declarations in case there are more than one.
            # This sometimes happens when a device sends its own XML config files.
            xml = etree.fromstring(self._remove_extraneous_xml_declarations(xml_str))
        except ValueError:
            # This can occur when requests returns a `str` (unicode) but there's also an XML
            # declaration, which lxml doesn't like.
            xml = etree.fromstring(xml_str.encode('utf8'))

        response = xml.find(".//{%s}%sResponse" % (self.service_type, action_name))
        if response is None:
            msg = ('Returned XML did not include an element which matches namespace %r and tag name'
                   ' \'%sResponse\'.' % (self.service_type, action_name))
            self._log.debug(msg + '\n' + etree.tostring(xml, pretty_print=True).decode('utf8'))
            raise SOAPProtocolError(msg)

        # Sometimes devices return XML strings as their argument values without escaping them with
        # CDATA. This checks to see if the argument has been parsed as XML and un-parses it if so.
        ret = {}
        for arg in response.getchildren():
            children = arg.getchildren()
            if children:
                ret[arg.tag] = b"\n".join(etree.tostring(x) for x in children)
            else:
                ret[arg.tag] = arg.text

        return ret
Construct the XML and make the call to the device. Parse the response values into a dict.