/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.fritzboxtr064.internal; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Iterator; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.soap.Detail; import javax.xml.soap.MessageFactory; import javax.xml.soap.Name; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPBodyElement; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPFault; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPMessage; import javax.xml.soap.SOAPPart; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.AuthCache; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.CookieStore; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.client.utils.URIBuilder; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.auth.DigestScheme; import org.apache.http.impl.client.BasicAuthCache; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.util.EntityUtils; import org.openhab.core.types.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /*** * Controls communication and parsing for TR064 communication with * FritzBox * * @author gitbock * @version 1.8.0 * */ public class Tr064Comm { private static final Logger logger = LoggerFactory.getLogger(Tr064Comm.class); private static final String DEFAULTUSER = "dslf-config"; // is used when no username is provided. private static final String TR064DOWNLOADFILE = "tr64desc.xml"; // filename of all available TR064 on fbox // access details for fbox private String _url = null; private String _user = null; private String _pw = null; // all services fbox offers private ArrayList<Tr064Service> _alServices = null; // mappig table for mapping item command to tr064 parameters private ArrayList<ItemMap> _alItemMap = null; // http client object used to communicate with fbox (needed for reading/writing soap requests) private CloseableHttpClient _httpClient = null; private HttpClientContext _httpClientContext = null; // for reusing auth (?) public Tr064Comm(String _url, String user, String pass) { // base URL from config this._url = _url; this._user = user; this._pw = pass; _alServices = new ArrayList<Tr064Service>(); _alItemMap = new ArrayList<ItemMap>(); init(); } public String getUrl() { return _url; } public void setUrl(String _url) { this._url = _url; } public String getUser() { return _user; } public void setUser(String _user) { this._user = _user; } public String getPw() { return _pw; } public void setPw(String _pw) { this._pw = _pw; } /*** * makes sure all values are set properly * before starting communications */ private void init() { if (_user == null) { _user = DEFAULTUSER; } if (_httpClient == null) { _httpClient = createTr064HttpClient(_url); // create http client used for communication } if (_alServices.isEmpty()) { // no services are known yet? readAllServices(); // can be done w/out item mappings and w/out auth } if (_alItemMap.isEmpty()) { // no mappings present yet? generateItemMappings(); } } /*** * Fetches a specific value from FritzBox * * * @param request string from config including the command and optional parameters * @return parsed value */ public String getTr064Value(String request) { String value = null; // extract itemCommand from request String[] itemConfig = request.split(":"); String itemCommand = itemConfig[0]; // command is always first // search for proper item Mapping ItemMap itemMap = determineItemMappingByItemCommand(itemCommand); if (itemMap == null) { logger.warn("No item mapping found for {}. Function not implemented by your FritzBox (?)", request); return ""; } // determine which url etc. to connect to for accessing required value Tr064Service tr064service = determineServiceByItemMapping(itemMap); // construct soap Body which is added to soap msg later SOAPBodyElement bodyData = null; // holds data to be sent to fbox try { MessageFactory mf = MessageFactory.newInstance(); SOAPMessage msg = mf.createMessage(); // empty message SOAPBody body = msg.getSOAPBody(); // std. SAOP body QName bodyName = new QName(tr064service.getServiceType(), itemMap.getReadServiceCommand(), "u"); // header // for body // element bodyData = body.addBodyElement(bodyName); // only if input parameter is present if (itemConfig.length > 1) { String dataInValue = itemConfig[1]; QName dataNode = new QName(itemMap.getReadDataInName()); // service specific node name SOAPElement beDataNode = bodyData.addChildElement(dataNode); // if input is mac address, replace "-" with ":" as fbox wants if (itemMap.getItemCommand().equals("maconline")) { dataInValue = dataInValue.replaceAll("-", ":"); } beDataNode.addTextNode(dataInValue); // add data which should be requested from fbox for this service } logger.trace("Raw SOAP Request to be sent to FritzBox: {}", soapToString(msg)); } catch (Exception e) { logger.error("Error constructing request SOAP msg for getting parameter. {}", e.getMessage()); logger.debug("Request was: {}", request); } if (bodyData == null) { logger.error("Could not determine data to be sent to FritzBox!"); return null; } SOAPMessage smTr064Request = constructTr064Msg(bodyData); // construct entire msg with body element String soapActionHeader = tr064service.getServiceType() + "#" + itemMap.getReadServiceCommand(); // needed to be // sent with // request (not // in body -> // header) SOAPMessage response = readSoapResponse(soapActionHeader, smTr064Request, _url + tr064service.getControlUrl()); logger.trace("Raw SOAP Response from FritzBox: {}", soapToString(response)); if (response == null) { logger.error("Error retrieving SOAP response from FritzBox"); return null; } // check if special "soap value parser" handler for extracting SOAP value is defined. If yes, use svp if (itemMap.getSoapValueParser() == null) { // extract dataOutName1 as default, no handler used NodeList nlDataOutNodes = response.getSOAPPart().getElementsByTagName(itemMap.getReadDataOutName()); if (nlDataOutNodes != null && nlDataOutNodes.getLength() > 0) { // extract value from soap response value = nlDataOutNodes.item(0).getTextContent(); } else { logger.error("FritzBox returned unexpected response. Could not find expected datavalue {} in response.", itemMap.getReadDataOutName()); logger.debug(soapToString(response)); } } else { logger.debug("Parsing response using SOAP value parser in Item map"); value = itemMap.getSoapValueParser().parseValueFromSoapMessage(response, itemMap, request); // itemMap is // passed for // accessing // mapping in // anonymous // method // (better way // to do??) } return value; } /*** * Sets a parameter in fbox. Called from event bus * * @param request config string from itemconfig * @param cmd command to set */ public void setTr064Value(String request, Command cmd) { // extract itemCommand from request String[] itemConfig = request.split(":"); String itemCommand = itemConfig[0]; // command is always first // search for proper item Mapping ItemMap itemMap = determineItemMappingByItemCommand(itemCommand); // determine which url etc. to connect to for accessing required value Tr064Service tr064service = determineServiceByItemMapping(itemMap); // construct soap Body which is added to soap msg later SOAPBodyElement bodyData = null; // holds data to be sent to fbox try { MessageFactory mf = MessageFactory.newInstance(); SOAPMessage msg = mf.createMessage(); // empty message SOAPBody body = msg.getSOAPBody(); // std. SAOP body QName bodyName = new QName(tr064service.getServiceType(), itemMap.getWriteServiceCommand(), "u"); // header // for // body // element bodyData = body.addBodyElement(bodyName); // only if input parameter is present if (itemConfig.length > 1) { String dataInValueAdd = itemConfig[1]; // additional parameter to set e.g. id of TAM to set QName dataNode = new QName(itemMap.getWriteDataInNameAdditional()); // name of additional para to set SOAPElement beDataNode = bodyData.addChildElement(dataNode); beDataNode.addTextNode(dataInValueAdd); // add value which should be set } // convert String command into numeric String setDataInValue = cmd.toString().equalsIgnoreCase("on") ? "1" : "0"; QName dataNode = new QName(itemMap.getWriteDataInName()); // service specific node name SOAPElement beDataNode = bodyData.addChildElement(dataNode); beDataNode.addTextNode(setDataInValue); // add data which should be requested from fbox for this service logger.debug("SOAP Msg to send to FritzBox for setting data: {}", soapToString(msg)); } catch (Exception e) { logger.error("Error constructing request SOAP msg for setting parameter. {}", e.getMessage()); logger.debug("Request was: {}. Command was: {}.", request, cmd.toString()); } if (bodyData == null) { logger.error("Could not determine data to be sent to FritzBox!"); return; } SOAPMessage smTr064Request = constructTr064Msg(bodyData); // construct entire msg with body element String soapActionHeader = tr064service.getServiceType() + "#" + itemMap.getWriteServiceCommand(); // needed to // be sent // with // request // (not in // body -> // header) SOAPMessage response = readSoapResponse(soapActionHeader, smTr064Request, _url + tr064service.getControlUrl()); if (response == null) { logger.error("Error retrieving SOAP response from FritzBox"); return; } logger.debug("SOAP response from FritzBox: {}", soapToString(response)); // Check if error received try { if (response.getSOAPBody().getFault() != null) { logger.error("Error received from FritzBox while trying to set parameter"); logger.debug("Soap Response was: {}", soapToString(response)); } } catch (SOAPException e) { logger.error("Could not parse soap response from FritzBox while setting parameter. {}", e.getMessage()); logger.debug("Soap Response was: {}", soapToString(response)); } } /*** * Creates a apache HTTP Client object, ignoring SSL Exceptions like self signed certificates * and sets Auth. Scheme to Digest Auth * * @param fboxUrl the URL from config file of fbox to connect to * @return the ready-to-use httpclient for tr064 requests */ private CloseableHttpClient createTr064HttpClient(String fboxUrl) { CloseableHttpClient hc = null; // Convert URL String from config in easy explotable URI object URIBuilder uriFbox = null; try { uriFbox = new URIBuilder(fboxUrl); } catch (URISyntaxException e) { logger.error("Invalid FritzBox URL! {}", e.getMessage()); return null; } // Create context of the http client _httpClientContext = HttpClientContext.create(); CookieStore cookieStore = new BasicCookieStore(); _httpClientContext.setCookieStore(cookieStore); // SETUP AUTH // Auth is specific for this target HttpHost target = new HttpHost(uriFbox.getHost(), uriFbox.getPort(), uriFbox.getScheme()); // Add digest authentication with username/pw from global config CredentialsProvider credp = new BasicCredentialsProvider(); credp.setCredentials(new AuthScope(target.getHostName(), target.getPort()), new UsernamePasswordCredentials(_user, _pw)); // Create AuthCache instance. Manages authentication based on server response AuthCache authCache = new BasicAuthCache(); // Generate DIGEST scheme object, initialize it and add it to the local auth cache. Digeste is standard for fbox // auth SOAP DigestScheme digestAuth = new DigestScheme(); digestAuth.overrideParamter("realm", "HTTPS Access"); // known from fbox specification digestAuth.overrideParamter("nonce", ""); // never known at first request authCache.put(target, digestAuth); // Add AuthCache to the execution context _httpClientContext.setAuthCache(authCache); // SETUP SSL TRUST SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); SSLConnectionSocketFactory sslsf = null; try { sslContextBuilder.loadTrustMaterial(null, new TrustSelfSignedStrategy()); // accept self signed certs sslsf = new SSLConnectionSocketFactory(sslContextBuilder.build(), null, null, new NoopHostnameVerifier()); // dont // verify // hostname // against // cert // CN } catch (Exception ex) { logger.error(ex.getMessage()); } // Set timeout values RequestConfig rc = RequestConfig.copy(RequestConfig.DEFAULT).setSocketTimeout(4000).setConnectTimeout(4000) .setConnectionRequestTimeout(4000).build(); // BUILDER // setup builder with parameters defined before hc = HttpClientBuilder.create().setSSLSocketFactory(sslsf) // set the SSL options which trust every self signed // cert .setDefaultCredentialsProvider(credp) // set auth options using digest .setDefaultRequestConfig(rc) // set the request config specifying timeout .build(); return hc; } /*** * converts SOAP msg into string * * @param sm * @return */ private String soapToString(SOAPMessage sm) { String strMsg = ""; try { ByteArrayOutputStream xmlStream = new ByteArrayOutputStream(); sm.writeTo(xmlStream); strMsg = new String(xmlStream.toByteArray()); } catch (Exception e) { e.printStackTrace(); } return strMsg; } /*** * * @param soapActionHeader String in HTTP Header. specific for each TR064 service * @param request the SOAPMEssage Object to send to fbox as request * @param serviceUrl URL to sent the SOAP Message to (service specific) * @return */ private SOAPMessage readSoapResponse(String soapActionHeader, SOAPMessage request, String serviceUrl) { SOAPMessage response = null; // Soap Body to post HttpPost postSoap = new HttpPost(serviceUrl); // url is service specific postSoap.addHeader("SOAPAction", soapActionHeader); // add the Header specific for this request HttpEntity entBody = null; HttpResponse resp = null; // stores raw response from fbox boolean exceptionOccurred = false; try { entBody = new StringEntity(soapToString(request), ContentType.create("text/xml", "UTF-8")); // add body postSoap.setEntity(entBody); resp = _httpClient.execute(postSoap, _httpClientContext); // Fetch content data StatusLine slResponse = resp.getStatusLine(); HttpEntity heResponse = resp.getEntity(); // Check for (auth-) error if (slResponse.getStatusCode() == 401) { logger.error( "Could not read response from FritzBox. Unauthorized! Check User/PW in config. Create user for tr064 requests"); return null; } // Parse response into SOAP Message response = MessageFactory.newInstance().createMessage(null, heResponse.getContent()); } catch (UnsupportedEncodingException e) { logger.error("Encoding not supported: {}", e.getMessage().toString()); response = null; exceptionOccurred = true; } catch (ClientProtocolException e) { logger.error("Client Protocol not supported: {}", e.getMessage().toString()); response = null; exceptionOccurred = true; } catch (IOException e) { logger.error("Cannot send/receive: {}", e.getMessage().toString()); response = null; exceptionOccurred = true; } catch (UnsupportedOperationException e) { logger.error("Operation unsupported: {}", e.getMessage().toString()); response = null; exceptionOccurred = true; } catch (SOAPException e) { logger.error("SOAP Error: {}", e.getMessage().toString()); response = null; exceptionOccurred = true; } finally { // Make sure connection is released. If error occurred make sure to print in log if (exceptionOccurred) { logger.error("Releasing connection to FritzBox because of error!"); } else { logger.debug("Releasing connection"); } postSoap.releaseConnection(); } return response; } /*** * sets all required namespaces and prepares the SOAP message to send * creates skeleton + body data * * @param bodyData is attached to skeleton to form entire SOAP message * @return ready to send SOAP message */ private SOAPMessage constructTr064Msg(SOAPBodyElement bodyData) { SOAPMessage soapMsg = null; try { MessageFactory msgFac; msgFac = MessageFactory.newInstance(); soapMsg = msgFac.createMessage(); soapMsg.setProperty(SOAPMessage.WRITE_XML_DECLARATION, "true"); soapMsg.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, "UTF-8"); SOAPPart part = soapMsg.getSOAPPart(); // valid for entire SOAP msg String namespace = "s"; // create suitable fbox envelope SOAPEnvelope envelope = part.getEnvelope(); envelope.setPrefix(namespace); envelope.removeNamespaceDeclaration("SOAP-ENV"); // delete standard namespace which was already set envelope.addNamespaceDeclaration(namespace, "http://schemas.xmlsoap.org/soap/envelope/"); Name nEncoding = envelope.createName("encodingStyle", namespace, "http://schemas.xmlsoap.org/soap/encoding/"); envelope.addAttribute(nEncoding, "http://schemas.xmlsoap.org/soap/encoding/"); // create empty header SOAPHeader header = envelope.getHeader(); header.setPrefix(namespace); // create body with command based on parameter SOAPBody body = envelope.getBody(); body.setPrefix(namespace); body.addChildElement(bodyData); // bodyData already prepared. Needs only be added } catch (Exception e) { logger.error("Error creating SOAP message for fbox request with data {}", bodyData); e.printStackTrace(); } return soapMsg; } /*** * looks for the proper item mapping for the item command given from item file * * @param itemCommand String item command * @return found itemMap object if found, or null */ private ItemMap determineItemMappingByItemCommand(String itemCommand) { ItemMap foundMapping = null; // iterate over all itemMappings to find proper mapping for requested item command Iterator<ItemMap> itMap = _alItemMap.iterator(); while (itMap.hasNext()) { ItemMap currentMap = itMap.next(); if (itemCommand.equals(currentMap.getItemCommand())) { foundMapping = currentMap; break; } } if (foundMapping == null) { logger.error("No mapping found for item command {}", itemCommand); } return foundMapping; } /*** * determines Service including which URL to connect to for value request * * @param the itemmap for which the service is searched * @return the found service or null */ private Tr064Service determineServiceByItemMapping(ItemMap mapping) { Tr064Service foundService = null; // search which service matches the item mapping Iterator<Tr064Service> it = _alServices.iterator(); while (it.hasNext()) { Tr064Service currentService = it.next(); if (currentService.getServiceId().contains(mapping.getServiceId())) { foundService = currentService; break; } } if (foundService == null) { logger.warn("No tr064 service found for service id {}", mapping.getServiceId()); } return foundService; } /*** * Connects to fbox service xml to get a list of all services * which are offered by TR064. Saves it into local list */ private void readAllServices() { Document xml = getFboxXmlResponse(_url + "/" + TR064DOWNLOADFILE); if (xml == null) { logger.error("Could not read xml response services"); return; } NodeList nlServices = xml.getElementsByTagName("service"); // get all service nodes Node currentNode = null; XPath xPath = XPathFactory.newInstance().newXPath(); for (int i = 0; i < nlServices.getLength(); i++) { // iterate over all services fbox offered us currentNode = nlServices.item(i); Tr064Service trS = new Tr064Service(); try { trS.setControlUrl((String) xPath.evaluate("controlURL", currentNode, XPathConstants.STRING)); trS.setEventSubUrl((String) xPath.evaluate("eventSubURL", currentNode, XPathConstants.STRING)); trS.setScpdurl((String) xPath.evaluate("SCPDURL", currentNode, XPathConstants.STRING)); trS.setServiceId((String) xPath.evaluate("serviceId", currentNode, XPathConstants.STRING)); trS.setServiceType((String) xPath.evaluate("serviceType", currentNode, XPathConstants.STRING)); } catch (XPathExpressionException e) { logger.debug("Could not parse service {}", currentNode.getTextContent()); e.printStackTrace(); } _alServices.add(trS); } } /*** * populates local static mapping table * todo: refactore to read from config file later? * sets the parser based on the itemcommand -> soap value parser "svp" anonymous method * for each mapping * */ private void generateItemMappings() { // services available from fbox. Needed for e.g. wifi select 5GHz/Guest Wifi if (_alServices.isEmpty()) { // no services are known yet? readAllServices(); } // Mac Online Checker ItemMap imMacOnline = new ItemMap("maconline", "GetSpecificHostEntry", "LanDeviceHosts-com:serviceId:Hosts1", "NewMACAddress", "NewActive"); imMacOnline.setSoapValueParser(new SoapValueParser() { @Override public String parseValueFromSoapMessage(SOAPMessage sm, ItemMap mapping, String request) { logger.debug("Parsing FritzBox response for maconline"); String value = ""; // maconline: if fault is present could also indicate not a fault but MAC is not known try { SOAPBody sbResponse = sm.getSOAPBody(); if (sbResponse.hasFault()) { SOAPFault sf = sbResponse.getFault(); Detail detail = sf.getDetail(); if (detail != null) { NodeList nlErrorCode = detail.getElementsByTagName("errorCode"); Node nErrorCode = nlErrorCode.item(0); String errorCode = nErrorCode.getTextContent(); if (errorCode.equals("714")) { value = "MAC not known to FritzBox!"; logger.debug(value); } else { logger.error("Error received from FritzBox: {}. SOAP request was: {}", soapToString(sm), request); value = "ERROR"; } } } else { SOAPBody sb = sm.getSOAPBody(); // parameter name to extract is taken from mapping NodeList nlActive = sb.getElementsByTagName(mapping.getReadDataOutName()); if (nlActive.getLength() > 0) { Node nActive = nlActive.item(0); value = nActive.getTextContent(); logger.debug("parsed as {}", value); } } } catch (SOAPException e) { logger.error("Error parsing SOAP response from FritzBox: {}", e.getMessage()); } return value; } }); _alItemMap.add(imMacOnline); _alItemMap.add(new ItemMap("modelName", "GetInfo", "DeviceInfo-com:serviceId:DeviceInfo1", "", "NewModelName")); _alItemMap.add(new ItemMap("wanip", "GetExternalIPAddress", "urn:WANPPPConnection-com:serviceId:WANPPPConnection1", "", "NewExternalIPAddress")); // Wifi 2,4GHz ItemMap imWifi24Switch = new ItemMap("wifi24Switch", "GetInfo", "urn:WLANConfiguration-com:serviceId:WLANConfiguration1", "", "NewEnable"); imWifi24Switch.setWriteServiceCommand("SetEnable"); imWifi24Switch.setWriteDataInName("NewEnable"); _alItemMap.add(imWifi24Switch); // wifi 5GHz ItemMap imWifi50Switch = new ItemMap("wifi50Switch", "GetInfo", "urn:WLANConfiguration-com:serviceId:WLANConfiguration2", "", "NewEnable"); imWifi50Switch.setWriteServiceCommand("SetEnable"); imWifi50Switch.setWriteDataInName("NewEnable"); // guest wifi ItemMap imWifiGuestSwitch = new ItemMap("wifiGuestSwitch", "GetInfo", "urn:WLANConfiguration-com:serviceId:WLANConfiguration3", "", "NewEnable"); imWifiGuestSwitch.setWriteServiceCommand("SetEnable"); imWifiGuestSwitch.setWriteDataInName("NewEnable"); // check if 5GHz wifi and/or guest wifi is available. Tr064Service svc5GHzWifi = determineServiceByItemMapping(imWifi50Switch); Tr064Service svcGuestWifi = determineServiceByItemMapping(imWifiGuestSwitch); if (svc5GHzWifi != null && svcGuestWifi != null) { // WLANConfiguration3+2 present -> guest wifi + 5Ghz present // prepared properly, only needs to be added _alItemMap.add(imWifi50Switch); _alItemMap.add(imWifiGuestSwitch); logger.debug("Found 2,4 Ghz, 5Ghz and Guest Wifi"); } if (svc5GHzWifi != null && svcGuestWifi == null) { // WLANConfiguration3 not present but 2 -> no 5Ghz Wifi // available but Guest Wifi // remap itemMap for Guest Wifi from 3 to 2 imWifiGuestSwitch.setServiceId("urn:WLANConfiguration-com:serviceId:WLANConfiguration2"); _alItemMap.add(imWifiGuestSwitch);// only add guest wifi, no 5Ghz logger.debug("Found 2,4 Ghz and Guest Wifi"); } if (svc5GHzWifi == null && svcGuestWifi == null) { // WLANConfiguration3+2 not present > no 5Ghz Wifi or Guest // Wifi logger.debug("Found 2,4 Ghz Wifi"); } // Phonebook Download // itemcommand is dummy: not a real item ItemMap imPhonebook = new ItemMap("phonebook", "GetPhonebook", "urn:X_AVM-DE_OnTel-com:serviceId:X_AVM-DE_OnTel1", "NewPhonebookID", "NewPhonebookURL"); _alItemMap.add(imPhonebook); // TAM (telephone answering machine) Switch ItemMap imTamSwitch = new ItemMap("tamSwitch", "GetInfo", "urn:X_AVM-DE_TAM-com:serviceId:X_AVM-DE_TAM1", "NewIndex", "NewEnable"); imTamSwitch.setWriteServiceCommand("SetEnable"); imTamSwitch.setWriteDataInName("NewEnable"); imTamSwitch.setWriteDataInNameAdditional("NewIndex"); // additional Parameter to set _alItemMap.add(imTamSwitch); // New Messages per TAM ID // two requests needed: First gets URL to download tam info from, 2nd contains info of messages ItemMap imTamNewMessages = new ItemMap("tamNewMessages", "GetMessageList", "urn:X_AVM-DE_TAM-com:serviceId:X_AVM-DE_TAM1", "NewIndex", "NewURL"); // SVP fetches desired infos imTamNewMessages.setSoapValueParser(new SoapValueParser() { @Override public String parseValueFromSoapMessage(SOAPMessage sm, ItemMap mapping, String request) { String value = ""; logger.debug("Parsing FritzBox response for TAM messages: {}", soapToString(sm)); try { SOAPBody sbResponse = sm.getSOAPBody(); if (sbResponse.hasFault()) { SOAPFault sf = sbResponse.getFault(); Detail detail = sf.getDetail(); if (detail != null) { logger.error("Error received from fbox while parsing TAM message info: {}. ", soapToString(sm)); value = "ERROR"; } } else { NodeList nlDataOutNodes = sm.getSOAPPart().getElementsByTagName(mapping.getReadDataOutName()); // URL if (nlDataOutNodes != null && nlDataOutNodes.getLength() > 0) { // extract URL from soap response String url = nlDataOutNodes.item(0).getTextContent(); Document xmlTamInfo = getFboxXmlResponse(url); logger.debug("Parsing xml message TAM info {}", Helper.documentToString(xmlTamInfo)); NodeList nlNews = xmlTamInfo.getElementsByTagName("New"); // get all Nodes containing "new", // indicating message was not // listened to // When <new> contains 1 -> message is new, when 0, message not new -> Counting 1s int newMessages = 0; for (int i = 0; i < nlNews.getLength(); i++) { if (nlNews.item(i).getTextContent().equals("1")) { newMessages++; } } value = Integer.toString(newMessages); logger.debug("Parsed new messages as: {}", value); } else { logger.error( "FritzBox returned unexpected response. Could not find expected datavalue {} in response {}", mapping.getReadDataOutName(), soapToString(sm)); } } } catch (SOAPException e) { logger.error("Error parsing SOAP response from FritzBox"); e.printStackTrace(); } return value; } }); _alItemMap.add(imTamNewMessages); // Missed calls // two requests: 1st fetches URL to download call list, 2nd fetches xml call list ItemMap imMissedCalls = new ItemMap("missedCallsInDays", "GetCallList", "urn:X_AVM-DE_OnTel-com:serviceId:X_AVM-DE_OnTel1", "NewDays", "NewCallListURL"); // svp for downloading call list from received URL imMissedCalls.setSoapValueParser(new SoapValueParser() { @Override public String parseValueFromSoapMessage(SOAPMessage sm, ItemMap mapping, String request) { String value = ""; logger.debug("Parsing FritzBox response for call list: {}", soapToString(sm)); // extract how many days of call list should be examined for missed calls String days = "3"; // default String[] itemConfig = request.split(":"); if (itemConfig.length == 2) { days = itemConfig[1]; // set the days as defined in item config. Otherwise default value of 3 is // used } try { SOAPBody sbResponse = sm.getSOAPBody(); if (sbResponse.hasFault()) { SOAPFault sf = sbResponse.getFault(); Detail detail = sf.getDetail(); if (detail != null) { logger.error("Error received from FritzBox while parsing call list: {}", soapToString(sm)); value = "ERROR"; } } else { NodeList nlDataOutNodes = sm.getSOAPPart().getElementsByTagName(mapping.getReadDataOutName()); // URL if (nlDataOutNodes != null && nlDataOutNodes.getLength() > 0) { // extract URL from soap response String url = nlDataOutNodes.item(0).getTextContent(); // only get missed calls of the last x days url = url + "&days=" + days; logger.debug("Downloading call list using url {}", url); Document xmlTamInfo = getFboxXmlResponse(url); // download call list logger.debug("Parsing xml message call list info {}", Helper.documentToString(xmlTamInfo)); NodeList nlTypes = xmlTamInfo.getElementsByTagName("Type"); // get all Nodes containing // "Type". Type 2 => missed // When <type> contains 2 -> call was missed -> Counting only 2 entries int missedCalls = 0; for (int i = 0; i < nlTypes.getLength(); i++) { if (nlTypes.item(i).getTextContent().equals("2")) { missedCalls++; } } value = Integer.toString(missedCalls); logger.debug("Parsed new messages as: {}", value); } else { logger.error( "FritzBox returned unexpected response. Could not find expected datavalue {} in response {}", mapping.getReadDataOutName(), soapToString(sm)); } } } catch (SOAPException e) { logger.error("Error parsing SOAP response from FritzBox: {}", e.getMessage()); } return value; } }); _alItemMap.add(imMissedCalls); } /*** * sets up a raw http(s) connection to Fbox and gets xml response * as XML Document, ready for parsing * * @return */ public Document getFboxXmlResponse(String url) { Document tr064response = null; HttpGet httpGet = new HttpGet(url); boolean exceptionOccurred = false; try { CloseableHttpResponse resp = _httpClient.execute(httpGet, _httpClientContext); int responseCode = resp.getStatusLine().getStatusCode(); if (responseCode == 200) { HttpEntity entity = resp.getEntity(); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); tr064response = db.parse(entity.getContent()); EntityUtils.consume(entity); } else { logger.error("Failed to receive valid response from httpGet"); } } catch (Exception e) { exceptionOccurred = true; logger.error("Failed to receive valid response from httpGet: {}", e.getMessage()); } finally { // Make sure connection is released. If error occurred make sure to print in log if (exceptionOccurred) { logger.error("Releasing connection to FritzBox because of error!"); } else { logger.debug("Releasing connection"); } httpGet.releaseConnection(); } return tr064response; } }