Messung des Stromverbrauchs mit Hilfe von Schaltsteckdosen.

Der Stromverbrauch von Geräten wird von Herstellern meist sehr allgemein angegeben. Wieviel Energie tatsächlich verbraucht wird, lässt sich oft nur mit einer Messung feststellen. Schaltbare Steckdosen ermöglichen es, den Stromverbrauch von Geräten festzustellen und erleichtern mit ihren Funktionen eine Messung über längere Zeiträume.

Dieser Beitrag beschreibt die Messung des Stromverbrauchs mit Hilfe von Schaltsteckdosen des Typs Shelly Plug S [1] anhand von Beispielen in Java [3].

Stromverbrauch

Der Bedarf an elektrischer Energie, auch der Strombedarf, ist die Menge an elektrischer Energie, die Elektrogeräte für ihren Betrieb benötigen. Die tatsächliche Umsetzung im Betrieb während eines definierten Zeitabschnitts wird als Stromverbrauch bezeichnet.

Die umgewandelte elektrische Energie W (von englisch work = Arbeit) wird gemessen in Wattsekunden oder auch Kilowattstunden. Sie ist die gesamte elektrische Arbeit, die während des betrachteten Zeitraums t bei der betrachteten Leistungsaufnahme P fließt. Bei einem gleich bleibenden Bedarf ist die umgewandelte Energie W das Produkt der elektrischen Leistung P mit der verstrichenen Zeit t:

W = Pt

Beispiel: Ein Haartrockner nimmt 2000 Watt (2 kW) elektrische Leistung auf. Wird der Haartrockner eine halbe Stunde (0,5 h) lang betrieben, beträgt der Bedarf an elektrischer Energie 2 kW x 0,5 h = 1 kWh (eine Kilowattstunde).

(Quelle: Wikipedia [2])

Messung

Die Definition des Stromverbrauchs lässt erkennen, dass eine fortlaufende Messung des Stromverbrauchs nötig ist, um die gesamte Arbeit festzustellen, die während des betrachteten Zeitraums bei der Leistungsaufnahme fließt. Die Shelly Plug S tut genau das: Sie misst den Stromverbrauch fortlaufend. Der Stand der fortlaufenden Messung wird in einem kumulierten Gesamtwert festgehalten und fortgeschrieben. Zudem wird die Arbeit über jeweils die letzen drei vollen Minuten fortlaufend festgehalten (Wattminuten).

Zum Abruf der Messdaten wird der folgende Uniform Resource Locator (URL) via HTTP GET aufgerufen:

URL zum Abruf der aktuellen Messdaten von einer Steckdose des Typs Shelly Plug S
http://192.168.178.78/meter/0

Die IP-Adresse in obigem Aufruf ist die Adresse der Steckdose im Heimnetz. Mit Java lässt sich der obige Aufruf wie folgt ausführen.

Code-Beispiel zum Abruf einer Messung
private String getMessung(String ip) {
  StringBuilder sb = new StringBuilder();
  try {
    URL url = new URL("http", ip, "/meter/0");
    HttpURLConnection c = (HttpURLConnection) url.openConnection();
    c.setRequestMethod("GET");
    int responseCode = c.getResponseCode();
    if (responseCode == 200) {
      BufferedReader in = new BufferedReader(new InputStreamReader(c.getInputStream()));
      String inputLine;
      while ((inputLine = in.readLine()) != null) {
        sb.append(inputLine);
      }
      in.close();
    } else {
      sb.append("Messung fehlgeschlagen, ").append(responseCode);
    }
  } catch (IOException ex) {
    sb.append("Messung fehlgeschlagen, ").append(ex.getLocalizedMessage());
  }
  return sb.toString();
}

Die Antwort des Abrufs liefert eine JSON-Struktur wie folgt:

Messdaten einer Steckdose des Typs Shelly Plug S im JSON-Format
{
  "power":14.02,
  "overpower":0.00,
  "is_valid":true,
  "timestamp":1671551931,
  "counters":[
    13.984,
    12.823,
    12.839],
  "total":426009
}

Die obige JSON-Struktur kann in ein Java-Objekt überführt werden. Dazu genügt eine Java-Klasse mit folgendem Inhalt.

eine Datenstruktur zur Aufnahme der Messdaten in einer Java-Klasse
public class ShellyPlugSStromMessung {
  public double power;
  public double overpower;
  public boolean is_valid;
  public long timestamp;
  public double[] counters;
  public int total;
}

Mit Einsatz von Google Gson [6] genügt folgender Java-Code, um die Messdaten zu lesen.

Lesen von Messdaten mittels Gson
private ShellyPlugSStromMessung getMessObjekt(String json) {
  Gson gson = new Gson();
  return gson.fromJson(json, ShellyPlugSStromMessung.class);
}

So gewonnene Daten können nun in Java weiterverarbeitet werden.

Messreihe

Zur Feststellung des Stromverbrauchs für einen bestimmten Zeitraum müssen über den entsprechenden Zeitraum hinweg immer wieder Messungen gemacht werden. Da eine Schaltsteckdose des Typs Shelly Plug S die Wattminuten der letzten drei vollen Minuten erfasst, bietet sich eine Messung alle drei Minuten als Intervall an. Die so alle drei Minuten entstehenden Messungen bilden eine Messreihe.

Zur Modellierung einer Messreihe dient die folgende auszugsweise dargestellte gleichnamige Java-Klasse.

die Klasse Messreihe
public class Messreihe {

  private transient Connection c;
  private transient final Timer timer;

  public Messreihe() {
    this.timer = new Timer();
  }

  public Timer getTimer() {
    return timer;
  }

  // hier folgen noch weitere Elemente,
  // die weiter unten betrachtet werden
}

Die Klasse Messreihe dient der Erfassung von Informationen zu einer Messreihe. Zentral ist zudem die Referenz zu einem Objekt der Klasse Timer, das zur Steuerung der Messungen verwendet wird, sowie die Referenz zur Verbindung zu einer Datenbank.

Datenbank

Um die gewonnenen Daten zur späteren Verwendung festhalten und auswerten zu können, bietet sich die Nutzung einer relationalen Datenbank an. Die Datenstruktur zur Aufnahme der Daten ist nachfolgend beschrieben.

Tabellen

Die Datenbanktabelle Messung dient zur Aufnahme eines Messergebnisses in der Struktur, wie sie Schaltsteckdosen des Typs Shelly Plug S liefern.

die Datenbank-Tabelle Messung
CREATE TABLE APP.MESSUNG
(
   MID   INTEGER     NOT NULL,
   MRID  INTEGER     NOT NULL,
   PW    DOUBLE      NOT NULL,
   OP    DOUBLE      NOT NULL,
   IV    BOOLEAN,
   TS    TIMESTAMP   NOT NULL,
   W1    DOUBLE,
   W2    DOUBLE,
   W3    DOUBLE,
   TTL   INTEGER
);

Die Tabelle Messreihe nimmt die Angaben einzelner Messreihen auf.

die Datenbank-Tabelle Messreihe
CREATE TABLE APP.MESSREIHE
(
   MRID      INTEGER        NOT NULL,
   MRSTATUS  INTEGER        NOT NULL,
   MRNAME    VARCHAR(250)   NOT NULL,
   MRIP      VARCHAR(15)    NOT NULL,
   MRSTART   TIMESTAMP,
   MREND     TIMESTAMP
);

In der Tabelle WMin wird der Stromverbrauch für jede Minute einzeln gespeichert. Auf diese Weise sind Auswertungen über längere Zeiträume einfacher bis auf einzelne Minuten herunterzubrechen.

die Datenbank-Tabelle WMin
CREATE TABLE APP.WMIN
(
   MID   INTEGER     NOT NULL,
   WMNR  INTEGER     NOT NULL,
   TS    TIMESTAMP   NOT NULL,
   WMIN  DOUBLE
);

DTO

Die Klasse Messreihe wird mit Gettern, Settern und Annotationen versehen, die Objekte der Klasse als Data Transfer Objects (DTO) [11] zum Lesen aus der und Schreiben in die Datenbank verwendbar werden lassen.

die Klasse Messreihe als DTO mit Annotationen
@DBTable(name="app.messreihe")
@DBPrimaryKey({"mrid"})
public class Messreihe {

  private transient Connection c;
  private transient final Timer timer;

  public Messreihe() {
    this.timer = new Timer();
  }

  public Connection getConnection() {
    return c;
  }

  public void setConnection(Connection c) {
    this.c = c;
  }

  public Timer getTimer() {
    return timer;
  }

  /* ----- DTO ----- */

  public static final int STATUS_RUNNING = 1;
  public static final int STATUS_ENDED = 2;

  private int mrid;
  private int mrstatus;
  private String mrname;
  private String ip;
  private Timestamp mrstart;
  private Timestamp mrend;

  public void setmrid(int wert) {
    mrid = wert;
  }

  @DBColumn(name = "mrid")
  public int getmrid() {
    return mrid;
  }

  public void setmrname(String wert) {
    mrname = wert;
  }

  @DBColumn(name = "mrname")
  public String getmrname() {
    return mrname;
  }

  // hier folgen noch weitere getter und setter

}

Mit der Klasse Messung wird ebenso verfahren.

die Klasse Messung als DTO mit Annotationen
@DBTable(name="app.messung")
@DBPrimaryKey({"mid"})
public class Messung {
  private int mid;
  private double pw;
  private double op;
  private boolean iv;
  private String ts;
  private double w1;
  private double w2;
  private double w3;
  private int ttl;

  public void setmid(int wert) {
    mid = wert;
  }

  @DBColumn(name = "mid")
  public int getmid() {
    return mid;
  }

  public void setpw(double wert) {
    pw = wert;
  }

  @DBColumn(name = "pw")
  public double getpw() {
    return pw;
  }

  // hier weitere getter und setter
}

Und auch die Klasse WattMinute dient als DTO.

die Klasse WattMinute
@DBTable(name="app.wmin")
@DBPrimaryKey({"mid","wmnr"})
public class WattMinute {
  private int mid;
  private int wmnr;
  private java.sql.Timestamp ts;
  private double wmin;

  public void setmid(int wert) {
    mid = wert;
  }

  @DBColumn(name = "mid")
  public int getmid() {
    return mid;
  }

  public void setwmnr(int wert) {
    wmnr = wert;
  }

  @DBColumn(name = "wmnr")
  public int getwmnr() {
    return wmnr;
  }

  public void setts(java.sql.Timestamp wert) {
    ts = wert;
  }

  @DBColumn(name = "ts")
  public java.sql.Timestamp getts() {
    return ts;
  }

  public void setwmin(double wert) {
    wmin = wert;
  }

  @DBColumn(name = "wmin")
  public double getwmin() {
    return wmin;
  }
}

In Verbindung mit einem Derby Network Server [7] ist nun alles bereit zur Durchführung und Steuerung von Messreihen.

Timestamp

Im Zusammenhang mit Messreihen ist der Zeitpunkt einzelner Messungen wichtig. Ein Zeit-Ausdruck wird mit Hilfe der Package java.sql mit dem Datentyp Timestamp [14] in Datenbanken gespeichert. Derby hat hierfür den gleichnamigen Datentyp [15].

Mit den Methoden getTime [16] und setTime [17] kann ein Java SQL Zeitstempel in einen Unix Zeitstempel verwandelt werden und umgekehrt. Die Klasse java.util.Date wiederum kann einen Unix Zeitstempel in ein Java-Objekt mit dem entsprechenden Datum und der entsprechenden Zeit fassen.

Die Methode valueOf [18] verwandelt einen String des Formats yyyy-[m]m-[d]d hh:mm:ss[.f…​] in einen Java-SQL-Timestamp. Die Angabe von 'fractional' Sekunden ist optional und kann weggelassen werden, wenn Sekundenbruchteile für den Anwednungsfall nicht nötig sind. Die führenden Nullen bei Monaten und Tagen können ebenfalls weggelassen werden.

Für Zeitangaben kann auf der Seite eines Webclients das String-Format 2022-12-28 17:00:00 genutzt werden, um beispielsweise beim Abruf von Ergebnissen einen über die Bedienoberfläche gewählten Start- oder Ende-Zeitpunkt anzugeben.

Zur Arbeit mit der zuvor beschriebenen Datenbank kommt die Klassenbibliothek BaseLink [10] zum Einsatz, die als zentralen Funktionsbaustein die Klasse PersistenceManager sowie Objekte der Klasse GenericRecord für das objektrelationale Mapping [9] vorsieht. Zudem unterstützt BaseLink die Verwendung von Konfigurationsdateien für die Angabe von SQL-Statements [13].

Deshalb sind Klassen im Kontext dieses Beitrag als Subklassen der Klasse DbActor angelegt, wenn sie auf die Datenbank zugreifen. DbActor vereinheitlicht für ihre Subklassen die Verwendung von BaseLink für Zugriffe auf die Datenbank.

In DbActor ist auch die in den Code-Beispielen dieses Beitrags verschiedentlich auftauchende Methode getNextId enthalten, wie sie in der Anleitung zu BaseLink für eigene Schlüssel [12] beschrieben ist.

Steuerung

Zur Steuerung von Messungen in Messreihen dient ein Objekt der Klasse MessTreiber sowie Objekte von dessen innerer Klasse MessTask. Ein MessTreiber hält je ein Objekt der Klasse GenericRecord für jede Klasse bereit, die vom MessTask für Datenbankzugriffe benötigt wird. Zudem wird im Konstruktor der Klasse ein PersistenceManager und ein Verzeichnis der benötigten SQL-Kommandos erzeugt.

Elemente und Konstruktor der Klasse MessTreiber
public class MessTreiber extends DbActor {

  public static final String SQL_KEY_MESSREIHE = "mrid";
  public static final int START_FAILED = -1;
  public static final long MESS_INTERVALL = 1000 * 58 * 3; // prod: 2 Minuten 54 Sekunden

  private final Record mrMapper = new GenericRecord(Messreihe.class);
  private final Record msMapper = new GenericRecord(ShellyPlugSStromMessung.class);
  private final Record wminMapper = new GenericRecord(WattMinute.class);

  private final Map<Integer, Messreihe> mrMap;

  public MessTreiber(String dbDriverName, String dbUrl) throws ClassNotFoundException, IOException {
    super(dbDriverName, dbUrl);
    mrMap = new HashMap();
  }

  // weitere Methoden

}

Messreihe starten

Eine Messreihe wird mit der Methode MessTreiber.start begonnen.

die Methode MessTreiber.start
  public static final long MESS_INTERVALL = 1000 * 58 * 3; // 2 Minuten 54 Sekunden

  public int start(String ip, String mrName) {
    Messreihe mr = new Messreihe();
    Connection c = db.getConnection();
    mr.setConnection(c);
    int mrid = getNextId(c, SQL_KEY_MESSREIHE);
    mr.setmrid(mrid);
    mr.setmrname(mrName);
    mr.setIp(ip);
    mr.setMrstatus(Messreihe.STATUS_RUNNING);
    mr.setMrstart(new Timestamp(new Date().getTime()));
    mr.setMrend(mr.getMrstart());
    mrMap.put(mrid, mr);
    db.insert(c, mr, mrMapper);
    MessTask mt = new MessTask();
    mt.setMrId(mrid);
    mr.getTimer().scheduleAtFixedRate(mt, 0, MESS_INTERVALL);
    return mrid;
  }

In der Methode MessTreiber.start wird zuallererst eine neue Messreihe erzeugt und vom PersistenceManager eine Datenbankverbindung abgerufen, die für die Dauer der Messreihe dieser für Datenbankzugriffe übergeben wird. Der MessTreiber 'merkt' sich die Messreihe zusammen mit allen laufenden Messreihen in einer Map und schreibt die Angaben zur erzeugten Messreihe in die Datenbank.

Zuletzt wird eine neue Instanz eines MessTasks erzeugt und mit dem Intervall MESS_INTERVALL gestartet, das mit 2 Minuten und 54 Sekunden angelegt ist. Mit diesem Intervall wird erreicht, dass durch eventuelle Schwankungen und Ungenauigkeiten bei der Zeitsteuerung keine der drei vollen Minuten verpasst wird, die von einer Schaltsteckdose aufgezeichnet werden.

Messungen

Messungen werden während des Laufs einer Messreihe alle 2 Minuten und 54 Sekunden vom Timer ausgelöst, der in der Methode MessTask.start gestartet wird.

die innere Klasse MessTask der Klasse MessTreiber
public class MessTask extends TimerTask {

  public static final String SQL_KEY_MESSUNG = "mid";

  private int mrid;

  public void setMrId(int mrid) {
    this.mrid = mrid;
  }

  // weitrere Methoden
}

Die innere Klasse MessTask der Klasse MessTreiber überschreibt als Subklasse der Klasse TimerTask hierfür deren Methode run. Sie wird von Timer-Objekten immer dann aufgerufen, wenn die Zeit für die Ausführung eines Tasks gekommen ist.

die Methode run der Klasse MessTask
public void run() {
  Messreihe mr = mrMap.get(mrid);
  Connection c = mr.getConnection();
  String messungJson = getMessung(mr.getIp());
  ShellyPlugSStromMessung sm = getMessObjekt(messungJson);
  if (sm instanceof ShellyPlugSStromMessung) {
    /*
    der Shelly-Timestamp ist in Sekunden,
    java.sql.Timestamp will die Zeit aber in Millisekunden
     */
    sm.timestamp = sm.timestamp * 1000;
    /*
    der Shelly-Timestamp ist aus unklaren Gruenden der
    Zeit eine Stunde voraus
    */
    sm.timestamp -= 1000 * 60 * 60;
    sm.setmid(getNextId(c, SQL_KEY_MESSUNG));
    sm.setmrid(mr.getmrid());
    db.insert(c, sm, msMapper);

    db.insert(getWattMinute(sm, 1, sm.getw1()), wminMapper);
    db.insert(getWattMinute(sm, 2, sm.getw2()), wminMapper);
    db.insert(getWattMinute(sm, 3, sm.getw3()), wminMapper);
  } // der else-zweig ist schon in der Subroutine abgedeckt
}

Bei der Messung kommen die zu Beginn des Beitrags beschriebenen Methoden zur Anwendung. Mit getMessung wird die Messung abgerufen. getMessObjekt überträgt das Ergebnis in ein Java-Objekt der Klasse ShellyPlugSStromMessung das dann in der Datenbank gespeichert wird.

Zur Vereinfachung der Auswertung werden die gemessenen Wattminuten der letzten drei vollen Minuten zusätzlich in einzelne Datensätze zerlegt und separat in der Datenbank gespeichert. Hierzu dient die Methode getWattMinute der Klasse MessTask.

die Methode getWattMinute
private WattMinute getWattMinute(ShellyPlugSStromMessung sm, int nr, double wert) {
  long millis = sm.getts().getTime();
  millis -= 1000 * 60 * nr;

  GregorianCalendar gc = new GregorianCalendar();
  gc.setTime(new Date(millis));
  gc.set(Calendar.SECOND, 0);

  WattMinute wmin = new WattMinute();
  wmin.setmid(sm.getmid());
  wmin.setwmnr(nr);
  wmin.setwmin(wert);
  wmin.setts(new Timestamp(gc.getTimeInMillis()));
  return wmin;
}

Messreihe stoppen

Zum Stoppen dient die Methode MessTreiber.stop.

die Methode zum Stoppen einer Messreihe
public boolean stop(int mrid) {
  Messreihe mr = mrMap.get(mrid);
  if (mr instanceof Messreihe) {
    Connection c = mr.getConnection();
    mr.getTimer().cancel();
    mr.setMrstatus(Messreihe.STATUS_ENDED);
    mr.setMrend(new Timestamp(new Date().getTime()));
    db.update(c, mr, mrMapper);
    db.closeConnectionFinally(c);
    mrMap.remove(mrid);
    return true;
  } else {
    // keine gueltige ID, nichts zu tun
    return false;
  }
}

Hier wird zunächst der Timer der Messreihe gestoppt, woraufhin aus dieser Messreihe keine weiteren Messungen mehr erfolgen. Dann wird der Eintrag zur Messreihe in der Datenbank als beendet aktualisiert, die Datenbankverbindung dieser Messreihe geschlossen und schließlich das Messreihen-Objekt aus der Liste der Klasse MessTreiber entfernt.

An dieser Stelle wurden alle Mittel zur Messung des Stromverbrauchs und des Speicherns von Messergebnissen betrachtet.

Bedienung

Der Aufwand zur Herstellung einer grafischen Bedienoberfläche ist für eine Anwendung wie die hier vorgestellte vergleichsweise hoch. Eine einfachere Alternative ist, die in diesem Beitrag vorgestellten Funktionen über eine HTTP-Schnittstelle zugänglich und damit z.B. mit einem Browser bedienbar zu machen.

Hierzu wird mit Hilfe der Klasse HttpServer der Java Ablaufumgebung ein eingebetteter HTTP-Server in das Messprogramm eingebaut und eine Programmschnittstelle (Application Programming Interface, API) auf deren Grundlage geschaffen, die via HTTP aufrufbare Kommandos bereitstellt.

Die Klasse HttpServer erlaubt das Erstellen von Objekten der Klasse HttpContext, die bestimmte URLs wie z.B. ew/strg/mr repäsentieren. Ausgeführt auf der lokalen Maschine localhost bspw. auf Port 8989 würde damit ein Objekt der Klasse HttpServer Aufrufe an http://localhost:8989/ew/strg/mr verarbeiten.

ew wäre hierbei das Kürzel für ein Messprogramm namens eWächter, wie es in diesem Beitrag beschrieben ist. strg wäre die Kennung des Handlers für die Steuerung und mr stünde für die Steuerung von Messreihen.

Zu einem solchen Kontext kann dann eine Klasse entworfen werden, die die Schnittstelle HttpHandler implementiert und in dieser Implementierung z.B. das Starten und Stoppen von Messreihen sowie das Auflisten laufender Messreihen realisiert.

API: Messreihen

Zum Starten und Stoppen von Messreihen sowie zum Auflisten laufender Messreihen jeweils über HTTP kann eine Klasse MessSteuerung dienen, die mit ihrer Methode handle die Schnittstelle HttpHandler implementiert.

die Klasse MessSteuerung und ihre Methode handle
public class MessSteuerung implements HttpHandler {

  public static final int ITEM = 3;
  public static final int CMD = 4;
  public static final int IP = 5;
  public static final int MRID = 5;
  public static final int NAME = 6;

  public static final String START = "start";
  public static final String STOP = "stop";
  public static final String LISTE = "liste";
  public static final String MESSREIHE = "mr";

  @Override
  public void handle(HttpExchange exchange) throws IOException {
    String[] elem = exchange.getRequestURI().getPath().split("/");
    if (elem[ITEM].equals(MESSREIHE)) {
      switch (elem[CMD]) {
        case START:
          startMessreihe(exchange, elem[IP], elem[NAME]);
          break;

        case STOP:
          stopMessreihe(exchange, Integer.parseInt(elem[MRID]));
          break;

        case LISTE:
          messreihenAuflisten(exchange);
          break;

        default:
          // unbekanntes Kommando
          break;
      }
    } else {
      // unbekanntes Element
    }
  }

  // weitere Methoden hier
}

In der Methode handle werden HTTP-Aufrufe verarbeitet, die vom HttpServer an diesen HttpHandler geleitet werden. Im Objekt der Klasse HttpExchange sind alle Bestandteile des jeweiligen HTTP-Aufrufs beschrieben. Ein Objekt der Klasse MessSteuerung zerlegt den URL-String jedes HTTP-Aufrufs und prüft, ob an der vorgesehenen Stelle die Kommandos start, stop oder liste enthalten sind. Folgende URLs werden auf diese Weise verarbeitet.

  • Messreihe starten: HTTP GET an /ew/strg/mr/start/[IP-Adresse]/[Name der Messreihe]

  • Messreihe stoppen: HTTP GET an /ew/strg/mr/stop/[Messreihen-ID]

  • Messreihen auflisten: HTTP GET an /ew/strg/mr/liste

Für die obigen Kommandos wirkt die Methode handle der Klasse MessSteuerung als Verteiler, der zum HTTP-Kommando die passende Methode aufruft. Trifft auf diese Weise zum Beispiel das Kommando start ein, wird die Methode MessSteuerung.startMessreihe aufgerufen.

die Methode MessSteuerung.startMessreihe
private void startMessreihe(HttpExchange exchange, String ip, String name) throws ClassNotFoundException, IOException {
  MessTreiber mt = getMessTreiber(exchange.getHttpContext());
  int id = mt.start(ip, name);
  antwortSenden(exchange, 200, "Messreihe laeuft, ID: " + id);
}

Beim Start einer Messreihe ziehen Objekte der Klasse MessSteuerung mit Hilfe der Methode getMessTreiber den in Verwendung befindlichen Messtreiber heran und starten mit dessen Methode start eine neue Messreihe. Die eindeutige Nummer der neuen Messreihe wird als Antwort zurückgegeben.

die Methode getMessTreiber
private MessTreiber getMessTreiber(HttpContext ctx) throws ClassNotFoundException, IOException {
  Object o = ctx.getAttributes().get(MESS_TREIBER);
  if (o instanceof MessTreiber) {
    return (MessTreiber) o;
  } else {
    AppProperties einst = (AppProperties) eobj;
    MessTreiber mr = new MessTreiber("drivername", "dburl");
    ctx.getAttributes().put(MESS_TREIBER, mr);
    return mr;
  }
}

Die Methode getMessTreiber entnimmt den aktuell verwendeten Messtreiber den Attributen des HttpContext. Ist dort noch kein Messtreiber vorhanden, wird ein Messtreiber erstellt, dort hinterlegt und dieser Messtreiber zurückgegeben.

Die Methode antwortSenden ist dafür zuständig, die HTTP-Antwort zu formulieren und zu senden.

die Methode antwortSenden
protected void antwortSenden(HttpExchange exchange, int code, String antwort) {
  byte[] bytes = antwort.getBytes();
  exchange.sendResponseHeaders(code, bytes.length);
  OutputStream os = exchange.getResponseBody();
  os.write(bytes);
  os.flush();
  os.close();
}

Die Methoden stopMessreihe und messreihenAuflisten verwenden analog der Methode startMessreihe den Messtreiber und sind zur Vervollständigung der Beschreibung zur Klasse MessSteuerung nachfolgend aufgeführt.

die Methoden stopMessreihe und messreihenAuflisten
private void stopMessreihe(HttpExchange exchange, int id) throws ClassNotFoundException, IOException {
  HttpContext ctx = exchange.getHttpContext();
  Object o = ctx.getAttributes().get(MESS_TREIBER);
  if (o instanceof MessTreiber) {
    MessTreiber mt = (MessTreiber) o;
    if(mt.stop(id)) {
      clear(ctx, mt);
      antwortSenden(exchange, "Messreihe ID " + id + " beendet");
    } else {
      antwortSenden(exchange, "Messreihe ID " + id + " nicht gefunden");
    }
  } else {
    antwortSenden(exchange, "Keine Messreihen in Betrieb");
  }
}

private void messreihenAuflisten(HttpExchange exchange) {
  HttpContext ctx = exchange.getHttpContext();
  Object o = ctx.getAttributes().get(MESS_TREIBER);
  if (o instanceof MessTreiber) {
    MessTreiber mt = (MessTreiber) o;
    Collection<Messreihe> reihen = mt.getMessreihen();
    Gson gson = new GsonBuilder().setPrettyPrinting().create();
    antwortSenden(exchange, gson.toJson(reihen));
  } else {
    antwortSenden(exchange, "Keine Messreihen.");
  }
}

Server

Damit eine wie im vorangegangenen Abschnitt beschriebene Steuerung von Messungen via HTTP erfolgen kann, wird eine Instanz der Klasse HttpServer erzeugt und gestartet.

Code-Beispiel zum Starten eines HTTP-Servers für die Mess-Steuerung
HttpServer server = HttpServer.create(new InetSocketAddress(8989), 0);
server.createContext("/ew/strg/mr", new MessSteuerung());
server.setExecutor(Executors.newFixedThreadPool(10));
server.start();

Mit dem obigen Code läuft ein in eine Java-Anwendung eingebetter HTTP-Server und verarbeitet HTTP-Anfragen an /ew/strg/mr, wie es der vorige Abschnitt beschreibt.

Ergebnisse auswerten

Ebenso, wie es mit der Klasse MessSteuerung gemacht wurde, lässt sich eine Klasse entwerfen, mit der die in der Datenbank gesammelten Ergebnisse ausgewertet werden können. Auswertungen sind in aller Regel zur jeweiligen Fragestellung passende SQL-Abfragen. Hier ein SQL-Statement zum Abruf der Wattminuten einer Messreihe über einen bestimmten Zeitraum.

SQL zum Abruf der Wattminuten einer Messreihe über einen Zeitraum
select wm.ts,wm.wmin
from app.messreihe as mr,app.messung as m,app.wmin as wm
where mr.mrid=m.mrid
and m.mid=wm.mid
and wm.ts <= ?
and wm.ts >= ?
and mr.mrid = ?
order by wm.ts asc

Dieser SQL-Code kann in einer Properties-Datei hinterlegt und verwendet werden, wie es die Anleitung von BaseLink beschreibt [13]. Die Fragezeichen im SQL-Ausdruck ersetzt BaseLink [10] zur Laufzeit mit Hilfe von Prepared Statements durch die entsprechenden Parameter.

Wichtig

Wird ein SQL-Ausdruck wie der obige in einer Properties-Datei hinterlegt, die als XML-Struktur kodiert ist, müssen manche Zeichen maskiert werden, damit sie verarbeitet werden können: &#62; für < und &#60; für >

Mit folgendem Beispielcode kann der Inhalt mit diesem SQL-Ausdruck mit BaseLink aus der Datenbank gelesen werden.

PersistenceManager db = new PersistenceManager();
db.setDriverName("drivername");
db.setDatabase("DB-URL");
List<List<String>> ergebnisse = db.select(sql, false, "2023-01-20 00:00:00", "2023-01-20 23:59:59", 16);

Der Inhalt von ergebnisse in obigem Code wird einfach zu Text umgewandelt wie z.B. in folgendem Beispiel.

private String zuText(List<List<String>> dbAuszug) {
  StringBuilder sb = new StringBuilder();
  Iterator<List<String>> i = dbAuszug.iterator();
  while(i.hasNext()) {
    List rec = i.next();
    Iterator<String> field = rec.iterator();
    while(field.hasNext()) {
      sb.append(field.next());
      sb.append(" ");
    }
    sb.append("\n");
  }
  return sb.toString();
}

Mit diesen wenigen Code-Beispielen ist bereits schnell ein HttpHandler nach dem zuvor geschilderten Baumuster erstellt, der gesteuert über HTTP-Aufrufe Messergebnisse in Textform ausgibt. Natürlich lassen sich zahlreiche andere Ausprägungen von Auswertungen denken, die zwar nicht minder einfach zu bewerkstelligen sind, aber den Rahmen dieses Beitrags sprengen würden.

Fazit

Schaltsteckdosen des Typs Shelly Plug S liefern hilfreiche Funktionen für die Messung des Stromverbrauchs über längere Zeiträume.

Der Beitrag zeigt, dass sich schnell und einfach die Messung in Messreihen nebst Steuerung über HTTP-Anfragen und den Browser realisieren und sich ebenso einfach ein Einblick in die gewonnenen Messdaten erzielen lässt.

Die Programmiersprache Java liefert ein universell verwendbares Werkzeug, das mit den ebenfalls in Java vorliegenden Hilfsmitteln Derby, BaseLink und Gson alles enthält, was für eine maschinelle Datenerfassung nötig ist.

Änderungsverlauf

Version 1

Fertiggestellt am 20. Januar 2023

Verweise