Wir als Unternehmen haben uns das Ziel gesetzt, durch Smart-Geräte den Energiebedarf unserer Büros langfristig zu senken.
Den Anfang machen die Switches von myStrom, die den Stromverbrauch an einer Steckdose messen und den Strom über ein Relais schalten können.
Die Aufgabe besteht nun darin die Daten der Switches zu sammeln und auszuwerten. Dafür entwickeln wir eine Webapplikation, welche uns die Daten der Switches sammelt und grafisch aufbereitet. Des Weiteren wollen wir einen Gamification Faktor schaffen indem wir die Geräte in Gruppen einteilen, um diese dann vergleichen zu können. Dadurch sollen unsere Mitarbeiter motiviert werden Strom beziehungsweise Energie einzusparen.
Einführung
Wir als Unternehmen haben uns das Ziel gesetzt, durch Smart-Geräte den Energiebedarf unserer Büros langfristig zu senken.
Den Anfang machen die Switches von myStrom, die den Stromverbrauch an einer Steckdose messen und den Strom über ein Relais schalten können.
Die Aufgabe besteht nun darin die Daten der Switches zu sammeln und auszuwerten. Dafür entwickeln wir eine Webapplikation, welche uns die Daten der Switches sammelt und grafisch aufbereitet. Des Weiteren wollen wir einen Gamification Faktor schaffen indem wir die Geräte in Gruppen einteilen, um diese dann vergleichen zu können. Dadurch sollen unsere Mitarbeiter motiviert werden Strom beziehungsweise Energie einzusparen.
Warum Switches von myStrom?
Auf der Suche nach smarten Geräten mit einer gut dokumentierten Schnittstelle sind wir auf die Geräte von myStrom gestoßen, welche uns für unsere Anforderungen eine gute API bieten.
Smart-Office Portal
Das Dashboard
Das Dashboard soll als Übersicht für alle Bereiche des Smart-Office Portals dienen. Im Fall der Switches bewegen wir uns im Bereich Strom, denkbar wäre auch Temperatur oder Helligkeit. Im Strombereich wird in einem gestapelten Balkendiagramm der Gesamtverbrauch pro Tag oder Stunde angezeigt. Jede Gruppe wird mit ihrem Anteil am Gesamtverbrauch anteilsmäßig in einem Balken dargestellt. Bei Änderung des Zeitraumes, werden die angezeigten Daten angepasst.
Devices & Groups


Auf der Geräteübersichtsseite können wir Gruppen erstellen. In diesen Gruppen können wir dann Geräte hinzufügen.
Die Gerätedetails
Auf der Detailseite eines Gerätes werden einige Daten zum Gerät selbst, aktuelle Daten sowie Langzeitdaten dargestellt. Die Langzeitdaten ändern sich je nach ausgewähltem Zeitraum.
Technische Umsetzung
Daten - abfragen
@Service
public class RestClient {
private final Logger logger;
private final WebClient client;
public RestClient(final Logger logger) {
this.logger = logger;
this.client = WebClient.create();
}
public Mono<MyStromSwitchReport> getReport(final String ip) {
logger.info("RestCLient.getReport({})", ip);
return client.get()
.uri("http://{ip}/report", ip)
.retrieve()
.bodyToMono(MyStromSwitchReport.class);
}
}
Um die Daten der Switches abzufragen benutzen wir einen Webclient. Die Switches bieten uns über ihre API folgenden Report, welchen wir in der Datenbank speichern.
{
"power": 231.52302551269531
"Ws": 248.57005310058594,
"relay": true,
"temperature": 22.983306884765625
}
power: Aktueller Stromverbrauch relay: Aktueller Zustand des internen Relais temperatur: Aktuelle Temperatur in der Nähe der switch. Besonders der letzte Wert ist für unser Monitoring interessant, da wir damit den durchschnittlichen Stromverbrauch tracken können.
Daten - sammeln
Damit wir kontinuierlich und automatisiert Daten sammeln, ohne dies selbst erledigen zu müssen, haben wir uns eine automatische Abfrage gebaut.
cron:
expression: "0 0/15 * * * *"
Dafür haben wir in den Applikationseigenschaften eine „Cron Expression“ angelegt, die für jedes Viertel einer Stunde gilt.
@Scheduled(cron = "${cron.expression}")
public void getDataScheduled() {
logger.info("MyStromSwitchDataHandler.getDataScheduled()");
List<MyStromSwitch> switches = switchMapper.findAll();
Map<Integer, MyStromSwitchReport> switchReportMap = new HashMap<>();
switches.forEach(s -> {
try {
MyStromSwitchReport data = this.restClient.getReport(
s.getSwitchIp()).block();
switchReportMap.put(s.getId(), data);
} catch ( Exception e) {
logger.error("MyStromSwitchDataHandler.getDataScheduled(): {}"
, e.getMessage());
}
});
switchDataMapper.insertFromMap(switchReportMap);
}
Die Cron Expression wird dafür genutzt, um die Funktion aufzurufen, welche die Reportdaten aller registrierten Switches in einer HashMap sammelt.
// Hinzufügen der SwitchDatas als Liste von Records -> 1xDatenbankzugriff
public final void insertFromMap(Map<Integer,MyStromSwitchReport> reportMap) {
logger.info("MyStromSwitchDataMapper.insertFromMap()");
List<MystromSwitchesDataRecord> dataRecordList = new ArrayList<MystromSwitchesDataRecord>();
reportMap.forEach( (k, v) -> {
MystromSwitchesDataRecord dataRecord = dslContext.newRecord(MYSTROM_SWITCHES_DATA);
dataRecord.setSwitcheIdFk(k);
dataRecord.setPower(v.getPower());
dataRecord.setRelay(v.numRelay());
dataRecord.setWs(v.getWs());
dataRecord.setTemperature(v.getTemperature());
dataRecordList.add(dataRecord);
});
dslContext.batchInsert(dataRecordList).execute();
}
Zum Schluss werden die gesammelten Reports in einem Batchlauf in die Datenbank geschrieben.
Daten - zusammenführen
public final List<MyStromSwitchData> getGroupedGroupData(final DataPeriod period, final String granularity) {
logger.info("MyStromSwitchDataMapper.getGroupedGroupData()");
double factor = getFactor(granularity);
SelectConditionStep<Record5<Timestamp, String, BigDecimal, BigDecimal, Integer>>
baseQuery = dslContext
.select(Tables.MYSTROM_SWITCHES_DATA.TIMESTAMP
.as("timestamp"),
value(granularity)
.as("granularity"),
(avg(Tables.MYSTROM_SWITCHES_DATA.WS)
.mul(factor))
.as("avgPower"),
(avg(Tables.MYSTROM_SWITCHES_DATA.TEMPERATURE))
.as("avgTemp"),
Tables.SWITCHES_IN_GROUPS.SMART_GROUP_ID
.as("groupId"))
.from(Tables.MYSTROM_SWITCHES_DATA)
.join(Tables.SWITCHES_IN_GROUPS)
.on(Tables.SWITCHES_IN_GROUPS.SWITCH_ID
.eq(Tables.MYSTROM_SWITCHES_DATA.SWITCHE_ID_FK))
.where(Tables.MYSTROM_SWITCHES_DATA.TIMESTAMP
.ge(new Timestamp(period.getStartDate().getTime())))
.and(Tables.MYSTROM_SWITCHES_DATA.TIMESTAMP
.le(new Timestamp(period.getEndDate().getTime())))
.andNot(Tables.MYSTROM_SWITCHES_DATA.WS
.eq(BigDecimal.valueOf(0)));
.on(Tables.SWITCHES_IN_GROUPS.SWITCH_ID.eq(Tables.MYSTROM_SWITCHES_DATA.SWITCHE_ID_FK))
.where(Tables.MYSTROM_SWITCHES_DATA.TIMESTAMP
.ge(new Timestamp(period.getStartDate().getTime())))
.and(Tables.MYSTROM_SWITCHES_DATA.TIMESTAMP
.le(new Timestamp(period.getEndDate().getTime())))
.andNot(Tables.MYSTROM_SWITCHES_DATA.WS
.eq(BigDecimal.valueOf(0)));
if (granularity.equals("hour")) {
return baseQuery.groupBy(day(Tables.MYSTROM_SWITCHES_DATA.TIMESTAMP),
hour(Tables.MYSTROM_SWITCHES_DATA.TIMESTAMP),
Tables.SWITCHES_IN_GROUPS.SWITCH_ID,
Tables.SWITCHES_IN_GROUPS.SMART_GROUP_ID)
.fetchInto(MyStromSwitchData.class);
} else {
return baseQuery.groupBy(day(Tables.MYSTROM_SWITCHES_DATA.TIMESTAMP),
Tables.SWITCHES_IN_GROUPS.SWITCH_ID,
Tables.SWITCHES_IN_GROUPS.SMART_GROUP_ID)
.fetchInto(MyStromSwitchData.class);
}
}
Je nach Zeitraum den wir in unserem User Interface gewählt haben, wird die Granularität (Stunde oder Tag) der geforderten Daten berechnet. Mit dieser Granularität und dem Zeitraum werden die Daten gruppiert und zusammengefasst von der Datenbank abgefragt und an unsere UI gesendet.