RaspberryPi – Temperatur messen und in einer Android Application anzeigen – Teil 2

Willkommen im zweiten Teil des Projekts – Temperatur messen und in einer Android Application anzeigen. Wie im ersten Teil bereits erwähnt, kümmern wir uns nun um die Übertragung der Datenbank Werte in die Android Application. Manche von euch haben eventuell schon den alten Beitrag für einen Java TCP Server gelesen und können jetzt mit einem gewissen Hintergrundwissen hier besser einsteigen. Im Endeffekt könnte man die Daten auch mit dem Server verarbeiten der sich bereits bei manchen von euch, um die Infrarot, Funk oder sonstige Aufgaben kümmert. ABER, der Server schickt momentan die Daten im Form von Strings die wir dann entsprechend verarbeiten. Für so viele Temperaturdaten pro Anfrage hatte ich kein Bock die Daten als String zu verarbeiten. Deshalb bin ich auf eine Übertragung von JSON Elementen umgestiegen. Los geht’s !

Teil 1: JSON Aufbau und wie gehen wir eig. vor?

Ein JSON Datensatz besteht im Endeffekt aus JSON Objekten und Arrays. Es ist mit einem XML Aufbau vergleichbar wird aber im Endeffekt sogar direkt als JavaScript Code definiert. Jedes JSON Objekt wird mit einer { Klammer eingeleitet und beendet. Ein Array wird mit einer [ Klammer eingeleitet und beendet. Am besten wir überlegen uns am Anfang welche Informationen wir bezüglich der Temperatur darstellen wollen und basteln und aus diesen Informationen entsprechende Client und Server basierte Objekte. Dadurch wird es denke ich einfacher alles zu verstehen. Nun also, welche Informationen wollen wir unserem Server am Ende entlocken:

1.) Da wäre natürlich die momentane Temperatur
2.) Die gespeicherten Temperatur Daten für den jeweils angezeigten Tag
3.) Eventuell Zusatzinformationen wie zB. wie viele Daten wurden seit Beginn der Aufzeichnung gespeichert, die höchste bzw. niedrigste gemessene Temperatur usw.

Nun schauen wir uns einmal an wie das JSON Objekt aussieht wenn die Anfrage an den Server geschickt wird und wie die Antwort des Servers aussieht.

Punkt 1 - Client
{
 "ID": "TEMP"
 "COMMAND": "CURRENTTEMP"
}
Punkt 1 - Server
{
 "CURRENTTEMP": "20.23"
}
---------------------------------------------------------------------------------------
Punkt 2 - Client
{
 "ID": "TEMP"
 "COMMAND": "TEMPFORDAY"
 "DATE": "2015-01-01"
}
Punkt 2 - Server
{
 "2015-01-01": [{"TIME": "00:00:00","TEMP": "12.12"},
                {"TIME": "02:00:00","TEMP": "13.13"},
                usw.
               ]
}
---------------------------------------------------------------------------------------
Punkt 3 - Client
{
 "ID": "TEMP"
 "COMMAND": "STATS"
}
Punkt 3 - Server
{
 "COUNTTEMP": "125"
 "MAXTEMP": {
             "DATE": "2015-06-01"
             "TIME": "12:00"
             "TEMP": "35.00"
            }
 "MINTEMP": {
             "DATE": "2015-12-12"
             "TIME": "12:00"
             "TEMP": "1.23"
            }
}

Man kann erkennen das jedes Objekt der Client Seite mit der „ID“ = „TEMP“ anfängt. Das benutzen wir um zu erkennen das es sich hier um eine Anfrage bezüglich der Temperatur handelt. Wollen wir später noch andere Sachen vom gleichen Server anfragen, können wir einfach die ID der Anfrage ändern. Der Key „COMMAND“ bezieht sich auf die Information die wir über die Temperatur erfahren wollen.Punkt 1 ist noch relativ einfach zu verstehen. Vom Client kommt eine Anfrage bezüglich der Temperatur (ID) und Anhand des „COMMAND“ Key können wir sehen das die aktuelle Temperatur (CURRENTTEMP) angefragt wird. Die Antwort sieht also einfach aus. Hier wird dann einfach nur die aktuelle Temperatur zurückgeschickt. Der Punkt 2 will alle Daten (TEMPFORDAY) zu dem mitgeschicktem Datum (DATE) erfahren. Die Antwort vom Server wird mit dem entsprechendem Datum und einem Array geliefert. Dabei ist jedes Element des Arrays ein Objekt bestehend aus zwei Keys. Zum einem die Uhrzeit (TIME) und die zu diesem Zeitpunkt gemessene Temperatur (TEMP). Punkt 3 will sozusagen die Zusatzinformationen (STATS). Der Server liefert also zum Beispiel alle Datensätze (COUNTTEMP) die gemessen worden sind. Oder jeweils die höchste – kleinste Temperatur und wann diese gemessen wurde. Man sieht der Aufbau ist verständlich und je nach der abgeschickten Anfrage kann man die gewünschten Informationen schön verpacken.

Teil 2: JAVA TCP JSON Server

Die JSON Objekte werden wir natürlich nicht manuell erstellen. Für die Verarbeitung benutzen wir entsprechende Bibliothek. Im Internet gibt es eine Vielzahl an Angeboten. Im Endeffekt brauchen wir aber nicht viel. Deshalb habe ich mich für die „json-simple“ Bibliothek entschieden. Sie ist klein und bietet alle Funktionen die wir für eine erfolgreiche Umsetzung benötigen. Lädt euch also die neuste Version herunter (Link) , erstellt ein neues Java Projekt (JavaTCPServer o.ä) und importiert die Bibliothek.Zuerst schauen wir uns die main Methode an:

public static void main(String[] args) throws IOException {
     @SuppressWarnings("resource")
     ServerSocket socket = new ServerSocket(5010);
     System.out.println("Socket open on port 5010");
     while (true){
          new TCPThread(socket.accept());
     }
}

Diejenigen die den Beitrag zu dem TCP Server gelesen haben werden das hier erkennen. Wir erstellen einen neuen Socket in diesem Beispiel auf dem Port 5010. Für jede eingehende Verbindung wird ein neuer Thread in der Klasse „TCPThread“ erstellt, welchem wir die Verbindung zu dem Client übergeben. Diese Klasse ist folgendermaßen aufgebaut:

public class TCPThread extends Thread{
     private Socket socket;
     private ObjectInputStream in;
     private ObjectOutputStream out;
 
     private JSONObject input;
     private JSONObject output;
 
     public TCPThread(Socket socket) {
          this.socket = socket;
          start();
     }
 
     public void run(){
          try {
               System.out.println("Client connect to Server: " + 
                    socket.getInetAddress().getHostName());
 
               out = new ObjectOutputStream(socket.getOutputStream());
               in = new ObjectInputStream(socket.getInputStream());
 
               input = (JSONObject) in.readObject();
               System.out.println("Input from Client:" + input.toJSONString());
 
               output = executeCommand(input);
               out.writeObject(output);
 
               out.close();
               in.close();
               socket.close();
               System.out.println("Connection closed");
 
          } catch (IOException | ClassNotFoundException e) {
               System.out.println("Exception on Server:" + e.toString());
          }  
      }
 
      @SuppressWarnings("unchecked")
      private JSONObject executeCommand(JSONObject input){
           JSONObject response = new JSONObject();
 
           switch(input.get("ID").toString()){
           case "TEMP":
                response = new Temp(input).getResponse();
                break;
           default:
                response.put("ERR","UNKNOWN ID");
                break;
           }
           System.out.println("Response from Server:" + response.toJSONString());
           return response;
      }
}

Wir werden die Klasse Schritt für Schritt durchgehen. Aber zuerst eine kleine Information bezüglich der Übertragung selbst. Was noch im meinem letzten Beitrag zu dem JAVA Server mithilfe von Text übertragen worden ist, wird in diesem Fall als JSON Objekt übertragen. Das bedeutet das beide Seiten (Client und Server) nicht mehr die Informationen über Strings austauschen sondern direkt als JSON Objekte. Das spart Zeit und erlaubt uns die Objekte direkt verarbeiten zu können. Nun zurück zu der Klasse. Zuerst müssen wir diese um die Klasse „Thread“ erweitern. Danach brauchen wir die Socket Verbindung. Also erstellen wir die Variable „socket“ vom Typ Socket. Für das eingehende Objekt brauchen wir die Variable „in“ vom Typ ObjectInputStream. Für das ausgehende Objekt entsprechend die Variable „out“ vom Typ ObjectOutputStream. Danach folgen zwei JSON Objekte „input“ für die Informationen vom Client und „output“ für die Antwort vom Server. Dann kommt der Konstruktor, wo wir unseren „socket“ mit übergebenen Socket überschreiben und die Methode „start()“, die den Thread mit der Methode „run()“ ausführt , starten. Hier wird dann der OutputStream des Sockets der Variable „out“ zugewiesen. Genauso wird dann der InputStream der Variable „in“ übergeben. Danach speichern wir das JSON Objekt vom Client mithilfe der „readObject()“ Methode in das JSON Objekt „in“ und geben uns diese Information als String in die Konsole aus. Der Variable „output“ wird das JSON Objekt zugewiesen welches wir von der Methode „executeCommand“ bekommen. Dieser Methode übergeben wir das JSONObjekt „input“ mit den Informationen vom Client aber dazu später mehr. Danach werden alle Objekte geschlossen und die Verbindung getrennt. Im Fall einer Exception geben wir uns diese über die Konsole aus. Kommen wir zu der Methode „executeCommand“. Hier erstellen wir zuerst das JSON Objekt „response“. Danach holen wir uns die „ID“ des Objekts vom Client mithilfe der „get()“ Methode und führen mit dieser eine Switch Case Abfrage durch. In unserem Fall behandeln wir erstmal nur die ID „TEMP“ die, wenn es zutrifft ein JSON Objekt von der Klasse „Temp“ bekommt. Dazu gleich mehr. Ist die ID unbekannt wird ein „ERR“ Key in das „response“ Objekt gesetzt mit der Information das die ID unbekannt ist. Danach geben wir uns die Antwort vom Server an den Client aus und liefern das „response“ Objekt zurück welches wir, wie oben bereits erwähnt, an den Client verschicken. Man sieht also sollte man gänzlich auf JSON setzten können in dieser Methode die unterschiedlichsten ID’s behandelt werden. Ich persönlich verarbeite nun auch die Infrarot, Funk uns sonstige Befehle über JSON. Nun kommen wir zu der Klasse „Temp“ die für die Funktionen rund um die Temperatur verantwortlich ist.

public class Temp {
 
     private JSONObject response = new JSONObject();
     private JSONObject input = new JSONObject();
     private Connection conn = null;
     public Temp(JSONObject input) {
          this.input = input;
          switch(input.get("COMMAND").toString()){
          case "CURRENTTEMP":
               getCurrentTemp();
               break;
          case "TEMPFORDAY":
               getTempForDay();
               break;
          case "STATS":
               getStats();
               break;
          }
     }

 @SuppressWarnings("unchecked")
 private void getStats() {
      String stmt_count = "SELECT COUNT(TEMP) FROM TEMPDATA";
      String stmt_min = "SELECT DATE,TIME,TEMP FROM TEMPDATA ORDER BY TEMP LIMIT 1";
    String stmt_max = "SELECT DATE,TIME,TEMP FROM TEMPDATA ORDER BY TEMP DESC LIMIT 1";
      ResultSet Stats;
      try {
           Stats = getDBData(stmt_count);
           if (Stats.next()) {
                response.put("COUNTTEMP",Stats.getString(1));
           }            
           Stats = getDBData(stmt_max);
           if (Stats.next()){
                JSONObject maxTemp = new JSONObject();
                maxTemp.put("DATE",Stats.getString("DATE"));
                maxTemp.put("TIME",Stats.getString("TIME"));
                maxTemp.put("TEMP",Stats.getString("TEMP"));
                response.put("MAXTEMP",maxTemp);
           }
           Stats = getDBData(stmt_min);
           if (Stats.next()){
                JSONObject minTemp = new JSONObject();
                minTemp.put("DATE",Stats.getString("DATE"));
                minTemp.put("TIME",Stats.getString("TIME"));
                minTemp.put("TEMP",Stats.getString("TEMP"));
                response.put("MINTEMP",minTemp);
           }
           Stats.close();
      } catch (ClassNotFoundException | SQLException e) {
           System.out.println("STATS:" + e.toString());
      }
 }

 @SuppressWarnings("unchecked")
 private void getTempForDay() {
      String stmt = "SELECT * FROM TEMPDATA WHERE DATE = '"
                         + input.get("DATE").toString() + "'";
      ResultSet TempForDayDB;
      try {
           TempForDayDB = getDBData(stmt);
           JSONArray TimeTemp = new JSONArray();
           while (TempForDayDB.next()){
                JSONObject TimeTempDB = new JSONObject();
                TimeTempDB.put("TIME",TempForDayDB.getString("TIME"));
                TimeTempDB.put("TEMP",TempForDayDB.getString("TEMP"));
                TimeTemp.add(TimeTempDB);
           }
           TempForDayDB.close();
           response.put(input.get("DATE").toString(),TimeTemp);
      } catch (ClassNotFoundException | SQLException e) {
           System.out.println("TEMPFORDAY:" + e.toString());
      }
 }

 @SuppressWarnings("unchecked")
 private void getCurrentTemp() {
      BufferedReader br;
      File[] devices = new File("/sys/bus/w1/devices").listFiles();
      ArrayList <Float> temps = new ArrayList<Float>();
      float temp = 0;
 
      for (int i = 0;i < devices.length;i++){
           if (devices[i].getName().startsWith("10-")){
                try {
                     br = new BufferedReader(new FileReader(devices[i] + "/w1_slave"));
                     String crc = br.readLine();
                     if (crc.endsWith("YES")){
                          temps.add(Float.valueOf(br.readLine().
                               replaceAll("^.*t=", ""))/1000);
                          br.close();
                     }
                } catch (IOException e) {
                     System.out.println("CURRENTTEMP:" + e.toString());
                }
           }
      }
      if (temps.size() > 0){
           for (int i = 0;i <temps.size();i++){
                temp += temps.get(i);
           }
           temp = (float) (Math.round((temp / temps.size()) * 100) / 100.0);
      }
      response.put("CURRENTTEMP",String.valueOf(temp));
 }
 
 private ResultSet getDBData(String stmt) throws ClassNotFoundException, SQLException{
      Class.forName("org.h2.Driver");
      conn = DriverManager.getConnection
                             ("jdbc:h2:~/RaspiH2;FILE_LOCK=NO");
       PreparedStatement ps = conn.prepareStatement(stmt);
       return ps.executeQuery();
 }
 
 public JSONObject getResponse() {
      if (conn != null){
			           try {
				                conn.close();
			           } catch (SQLException e) {
			                	System.out.println("DBClose:" + e.toString());
			           }
		       }
      return response;
 }
}

Diese gehen wir auch Punkt für Punkt durch.Zuerst erstellen wir zwei JSON Objekte „response“ und „input“. Das Objekt „response“ wird im Endeffekt von dieser Klasse zurückgeliefert um dann später an den Client verschickt zu werden. Das Objekt „input“ ist das Objekt welches wir vom Client bekommen haben und in der Methode „executeCommand“ dieser Klasse übergeben haben. Im Konstruktor der Klasse holen wir uns den „COMMAND“ Eintrag aus dem JSON Objekt und schauen was der Client von dem Server will. Es werden also die zuvor besprochenen Möglichkeiten behandelt die jeweils eine eigene Methode bekommen. Fangen wir mit der ersten Methode an „getCurrentTemp()“ die uns die momentane Temperatur ausgibt. Hier brauchen wir aber soweit nichts verändern wir kennen diese Methode aus dem ersten Teil. Hier wird einfach je nach angeschlossenen Sensoren die durchschnittliche Temperatur ausgelesen. Einziger Unterschied? Im Fall einer Exception wird diese über die Konsole ausgegeben und am Ende der Methode, falls alles durchgelaufen ist, die aktuelle Temperatur als „CURRENTTEMP“ in das Objekt eingefügt. Bevor wir mit den zwei restlichen Methoden anfangen wirft bitte ein Blick auf die Methode „getDBData(String stmt)“. Diese Methode liefert uns ein ResultSet einer Query. Da beiden Methoden (STATS und TEMPFORDAY) auf die DB zugreifen erspart uns diese Methode alles zwei mal zu schreiben. Wir übergeben nur den SQL Ausdruck an die Methode und sie liefert uns die entsprechenden Datensätze zurück. Kommen wir jetzt zu der Methode „getTempForDay()“. Diese Methode soll uns alle gemessenen Daten zu dem gewünschtem Datum zurückliefern. Zuerst brauchen wir also den SQL Ausdruck die uns diese Datensätze liefert. Das Datum bekommen wir vom Client zugeschickt. Wir erstellen ein ResultSet und füllen ihn über die oben angesprochene Methode „getDBData“ welcher wir den String „stmt“ übergeben. Danach brauchen wir ein JSON Array wo wir die Daten speichern werden. Also gehen wir in einer While Schleife alle Datensätze durch. Bei jedem Durchlauf wird ein JSON Objekt (TimeTempDB) erstellt welchem wir die Uhrzeit „TIME“ und die gemessene Temperatur „TEMP“ übergeben. Dieses Objekt wird dann dem Array (TimeTemp) hinzugefügt. Sind alle Datensätze durch und die Schleife beendet wird das Array dem Objekt „response“ übergeben unter dem Datum vom Objekt „input“. Bei einer Exception wird dann wie vorher schon über die Konsole ausgegeben. Die letzte Methode „getStats“ soll uns drei Informationen liefern. Dafür brauchen wir entsprechend drei SQL Statements – „stmt_count“ gibt alle gemessenen Datensätze zurück – „stmt_min“ gibt die kleinste gemessene Temperatur aus – „stmt_max“ die höchste Temperatur. Danach folgt wieder ein ResultSet welches wir je nach dem SQL Ausdruck mit Datensätzen aus der DB füllen. Zuerst also „stmt_count“. Sind Datensätze da dann wird das „response“ Objekt mit „COUNTTEMP“ und der Anzahl der Datensätze gespeichert. Danach folgt „stmt_max“. Hier wird ein JSON Objekt erstellt dem wir die höchste gemessene Temperatur übergeben „TEMP“ und wann sie gemessen worden ist „DATE“ und „TIME“. Dieses Objekt speichern wir in das „response“ Objekt unter dem Key „MAXTEMP“. Das gleiche machen wir dann mit der „stmt_min“ Abfrage. Nun haben wir alle Methoden durch. Am Ende der Klasse ist dann nur noch der Getter „getResponse()“ der uns hoffentlich das mit Daten gefüllte „response“ JSON Objekt liefert. Am Ende gehen wir nochmal eine mögliche Anfrage durch:

1.) Client will die Statistik haben.
2.) Client erstellt ein entsprechendes JSONObjekt und schickt es an den Server.
3.) Server bekommt das Objekt und erkennt Anhand der ID das es sich um eine Temperatur Anfrage handelt.
4.) Es wird ein Objekt der Klasse „Temp“ erstellt wo erkannt wird das man
die Zusatzinformationen haben will.
5.) Es wird die Methode „getStats“ aufgerufen und das JSON Objekt „response“ wird
gefüllt.
6.) Das „response“ Objekt wird an das JSON Objekt aus der „executeCommand“ Methode über geben. Diese übergibt es weiter an den Thread der das Objekt zurück an
den Client schickt.
7.) Der Client bekommt die Antwort vom Server wertet die Daten aus und zeigt diese
entsprechend an.

Und so sieht die Abfrage dann in der Konsole aus:

jsonMan kann sehen das die Reihenfolge der „Keys“ durcheinander ist. Aber uns kann das ja egal sein, weil wir diese eben direkt über den Namen ansprechen können. Das war’s mit dem zweiten Teil. Im nächsten Beitrag setzen wir uns an die APP dran. Ich hoffe es ist jetzt nicht zu viel geworden. Solltet ihr Fragen haben stehe ich, natürlich soweit ich Zeit habe, zur Verfügung.

Kleine Verbesserung 19.05.2015:

Das Connection Objekt als Klassenvariable deklariert welches geschlossen wird falls wir die getDBData aufrufen. Den Code entsprechend angepasst.

TEIL 1TEIL 2TEIL 3

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.