Messanwendung mit dem Raspberry Pi, SHT21, Play!, WebSockets, NVD3.js. Teil 3: Implementierung der Webanwendung
Im dritten Teil der Blog-Serie möchte ich einen Auszug aus dem Quellcode der Webanwendung beschreiben. Im zweiten Blogeintrag ist zu sehen, dass wir die Methode newValues der Play!-Anwendung mit den Parametern hum(Luftfeuchtigkeit) und temp(Temperatur) aufrufen. Diese ist die einzige Methode, die von der C-Anwendung aufgerufen wird und dient zur Übergabe der Messwerte an die Webanwendung.
Die Implementierung im Controller ist einfach und kann dem folgenden Listing entnommen werden.
public static Result newValues(String hum, String temp) {
double dHum = Double.valueOf(hum);
double dTemp = Double.valueOf(temp);
DecimalFormat df = new DecimalFormat(".00");
hum=df.format(dHum);
temp=df.format(dTemp);
MeasureRoom.push(dHum, dTemp,timeInMillis);
MeasureRoom.humToSave = dHum;
MeasureRoom.tempToSave = dTemp;
String timeInSeconds = session().get("timeInSeconds");
int intTimeInSeconds =timeInMillis/1000;
if(timeInSeconds==null){
session("timeInSeconds", String.valueOf(timeInMillis/1000));
}else{
intTimeInSeconds = Integer.valueOf(session().get("timeInSeconds"));
}
return ok(""+intTimeInSeconds+"");
}
Die Klasse ChatRoom.java der Beispielanwendung(websocket-chat) des Play!-Frameworks wurde als Muster übernommen und dem Use-Case angepasst. Es werden hier hauptsächlich die Unterschiede zwischen der Beispielanwendung und der eigenen Implementierung erläutert. Der technisch interessierte Leser kann gerne auf GitHub den Quellcode vergleichen. Die Klassenvariable defaultRoom ist das zentrale Element der Klasse MeasureRoom und wird für Verteilung der Messdaten an die WebSocket- Clients verwendet. Die Mitglieder des MeasureRooms werden durch die HashMap members vorgehalten. Die Klassenvariablen humToSave und tempToSave dienen als Zwischenspeicher für die zuletzt gesetzten Werte(von der C-Messanwendung), um diese bei einer TICK- Nachricht durch den definierten Akka- Scheduler persistieren zu können. Die Methode push dient dazu alle registrierten WebSocket- Clients des MeasureRooms über die neuen Werte mittels der tell Methode des defaultRoom zu benachrichtigen.
// Actor Referenz mit der Klasse MeasureRoom
static ActorRef defaultRoom = Akka.system().actorOf(new Props(MeasureRoom.class));
// Registierte Mitglieder des Messraums.
Map<String, WebSocket.Out<JsonNode>> members = new HashMap<String, WebSocket.Out<JsonNode>>();
// zwei Klassenvariablen mit den zuletzt gesetzten Werten für Luftfeuchtigkeit und Temperatur
public static double humToSave = 0;
public static double tempToSave = 0;
// Sende eine TICK nachricht alle 15 Sekunden
static {
Akka.system().scheduler().schedule(
Duration.Zero(),
Duration.create(15000, MILLISECONDS),
defaultRoom, "TICK", Akka.system().dispatcher()
);
}
// Aufruf vom Controller aus, um neue Werte zu veröffentlichen
public static void push(double hum, double temp, int timeinterval){
defaultRoom.tell(new Talk(String.valueOf(hum), String.valueOf(temp), String.valueOf(timeinterval)));
}
Wir benutzen den TICK Event vom Akka Scheduler, um alle 15 Sekunden die Messwerte zu speichern, hierzu wurde die Methode onReceive erweitert. Die gespeicherten Messwerte stehen somit zur späteren Visualisierung zur Verfügung. Die Methode onReceive wird ausgeführt, sobald eine Nachricht von einem Fremdsystem (C-Mess-Programm, WebSocket-Clients) oder dem Scheduler kommt. So zum Beispiel, wenn ein WebSocket-Client den Messraum betritt, die Mesanwendung auf dem RaspberryPi Werte an die Webanwendung über die Methode newValues übermittelt und wenn der Akka- Scheduler einen TICK-Event ausführt.
// Actor Referenz mit der Klasse MeasureRoom
if("TICK".equals(message)) {
if(this.humToSave > 0){
MeasurePoint measurePoint= new MeasurePoint();
measurePoint.hum = this.humToSave;
measurePoint.temp = this.tempToSave;
measurePoint.measureTime = new Date(System.currentTimeMillis());
measurePoint.save();
}
}
MeasurePoint ist eine einfache Klasse (Modell), deren Objekte mit Hilfe von Ebean gespeichert werden können.
// Actor Referenz mit der Klasse MeasureRoom
@Entity
public class MeasurePoint extends Model {
@Id
public Long id;
@Constraints.Required
public double hum;
@Constraints.Required
public double temp;
@Formats.DateTime(pattern="dd-MM-yyyy hh-mm-ss")
public Date measureTime;
@Transient
public Long measureTimeAsLong;
public static Finder<long easurepoint=""> find = new Finder<long easurepoint="">(Long.class, MeasurePoint.class);
public Long getMeasureTimeAsLong(){
return measureTime.getTime();
}
}
</long></long>
Die Signatur der Methode notifyAll wurde um einen weiteren Parameter mit dem Namen timeintervall ergänzt. Die bestehenden Parameter wurden entsprechend der Anwendungsdomäne umbenannt. Innerhalb der Methode notifyAll wurde der neu eingeführte Parameter dem Objekt "event" hinzugefügt. Somit steht den WebSocket-Clients die Information zur Verfügung, in welchem Intervall die Abfrage des Sensors erfolgt. Dies ist für den korrekten Aufbau der Grafik, im Browser, notwendig. Die Methode notifyAll benachrichtigt alle WebSocket-Clients über neue Messwerte.
// Actor Referenz mit der Klasse MeasureRoom
public void notifyAll(String kind, String hum, String temp, String timeinterval) {
for(WebSocket.Out<jsonnode> channel: members.values()) {
ObjectNode event = Json.newObject();
....
event.put("hum", hum);
event.put("temp", temp);
event.put("timeinterval", timeinterval);
...
channel.write(event);
...
</jsonnode>
Ebenfalls an die Anwendungsdomäne wurde die Klasse Talk angepasst.
// Actor Referenz mit der Klasse MeasureRoom
public static class Talk {
final String timeinterval;
final String hum;
final String temp;
public Talk(String hum, String temp, String timeinterval) {
this.hum = hum;
this.temp = temp;
this.timeinterval = timeinterval;
}
}