Samstag, 21. Februar 2015

Fritz!Box Phonelist Microservice with Spring Boot

While making my home smart and automated I wanted to see the phonelist from my Fritz!Box 7362SL in my home visualization as well. Since I didn't find a javascript library for accessing the Fritz!Box via a Challenge-response authentication to put directly into my visualization page I later came across a Java implementation on https://github.com/grundid/fritzbox-java-api. This library is currently able to login into a Fritz!Box and modifying the Wifi Guest-Acces. I've added an additional method inside the FritzTemplate class for fetching the phonelist as a CSV stream.
public List<CallEntry> getPhoneList() {
    if (sessionId == null) {
        getSessionId();
    }
    List<CallEntry> result = new ArrayList<CallEntry>();
    ResponseEntity<String> response = restOperations.getForEntity(baseUrl + "/fon_num/foncalls_list.lua?sid={sid}&csv=", String.class, sessionId);
    
    String lines[] = response.getBody().split("\\r?\\n");
    for(int i=2;i <lines.length; i++) {
        String entries[] = lines[i].split(";");
        CallEntry callEntry = new CallEntry(Integer.parseInt(entries[0]), entries[1], entries[2], entries[3], entries[6]);
        result.add(callEntry);
    }
    return result;
}
Because I wanted to fetch the phonelist directly from a html page and I liked how simple a Spring RESTful service was implemented I made a RESTful microservice with Spring Boot. This microservice could be run automatically on startup with when my OpenHAB server start and provide an interface for fetching the phonelist from my Fritz!Box. At first I've implemented a resource representation class CallEntry:
package de.grundid.fritz.entity;

public class CallEntry {

 private String date = "";
 private String name = "";
 private String number = "";
 private String duration = "";
 private int type = 0;
 
 public CallEntry(int type, String date, String name, String number, String duration) {
  this.type = type;
  this.date = date;
  this.name = name;
  this.number = number;
  this.duration = duration;
 }
 
 public String getDuration() {
  return duration;
 }
 
 public void setDuration(String duration) {
  this.duration = duration;
 }
 
 public String getNumber() {
  return number;
 }
 
 public String getName() {
  return name;
 }
 
 public String getDate() {
  return date;
 }

 public int getType() {
  return type;
 }
}

After that I've implemented a controller class which handles the HTTP requests and returns a collection with the resources. The password for my Fritz!Box is stored in a property file named application.properties. This password will be injected into the password field.
package de.grundid.fritz.service;

import java.util.Collection;
import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import de.grundid.fritz.FritzTemplate;
import de.grundid.fritz.entity.CallEntry;

@RestController
public class CallListController {

 @Value("${fritz.password}")
 private String password;
 
 @RequestMapping(value = "/calllist/{limit}",method=RequestMethod.GET)
 public Collection<CallEntry> getCallListAsCSV(@PathVariable long limit) {
  RestTemplate restTemplate = new RestTemplate();
  FritzTemplate template = new FritzTemplate(restTemplate, password);
  List<CallEntry> phoneList = template.getPhoneList();
  if (phoneList.size() <= limit) {
   return phoneList;
  }
  return  phoneList.subList(0, (int) (limit - 1));
 }
}
And finally to make this Spring project runnable by its own I've implemented a CallListApp with a main method:
package de.grundid.fritz.service;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
@EnableAutoConfiguration
public class CallListApp {

    public static void main(String[] args) {
        SpringApplication.run(CallListApp.class, args);
    }
}
This class can be executed and Spring starts an embedded tomcat server which handles the HTTP requests on port 8080. To access the service from a different domain or different port by a javascript request I needed to activate CORS on the service side. For that I've added a Filter which addss particular header entries into the response.
package de.grundid.fritz.service;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

@Component
public class SimpleCORSFilter implements Filter {

        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
            HttpServletResponse response = (HttpServletResponse) res;
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
            response.setHeader("Access-Control-Max-Age", "3600");
            response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
            chain.doFilter(req, res);
        }

        public void init(FilterConfig filterConfig) {}

        public void destroy() {}

}
This was it and after that I could enjoy the complete calling list on my Smarthome Visualization page. If you are interested in using this service I've added a zip file with my eclipse project. https://drive.google.com/file/d/0B4uu0M3bXvqkY2pUTzRadUFGc28/view?usp=sharing

0 Kommentare:

Kommentar veröffentlichen