Eltern SmartMeter Wattwächter JSON + Demo Data
This commit is contained in:
parent
25c9c372a6
commit
524d03174a
@ -6,9 +6,11 @@ import de.ph87.data.plot.axis.Axis;
|
||||
import de.ph87.data.plot.axis.AxisRepository;
|
||||
import de.ph87.data.plot.axis.graph.Graph;
|
||||
import de.ph87.data.plot.axis.graph.GraphRepository;
|
||||
import de.ph87.data.plot.axis.graph.GraphType;
|
||||
import de.ph87.data.series.Series;
|
||||
import de.ph87.data.series.SeriesRepository;
|
||||
import de.ph87.data.series.SeriesType;
|
||||
import de.ph87.data.topic.TimestampType;
|
||||
import de.ph87.data.topic.Topic;
|
||||
import de.ph87.data.topic.TopicRepository;
|
||||
import de.ph87.data.topic.query.TopicQuery;
|
||||
@ -64,6 +66,8 @@ public class DemoService {
|
||||
final Series electricityPowerProduce = series("electricity/power/produce", "W", SeriesType.VARYING, 5);
|
||||
topicMeterNumber(
|
||||
"openDTU/pv/patrix/json2",
|
||||
TimestampType.EPOCH_SECONDS,
|
||||
"$.timestamp",
|
||||
"$.inverter",
|
||||
new TopicQuery(electricityEnergyProduce, "$.totalKWh"),
|
||||
new TopicQuery(electricityPowerProduce, "$.totalW")
|
||||
@ -75,6 +79,8 @@ public class DemoService {
|
||||
final Series electricityPowerDelivery = series("electricity/power/delivery", "W", SeriesType.VARYING, 5);
|
||||
topicMeterNumber(
|
||||
"electricity/grid/json",
|
||||
TimestampType.EPOCH_SECONDS,
|
||||
"$.timestamp",
|
||||
"\"1ZPA0020300305\"",
|
||||
new TopicQuery(electricityEnergyPurchase, "$.purchaseWh", 0.001),
|
||||
new TopicQuery(electricityPowerPurchase, "$.powerW", TopicQueryFunction.ONLY_POSITIVE),
|
||||
@ -82,6 +88,21 @@ public class DemoService {
|
||||
new TopicQuery(electricityPowerDelivery, "$.powerW", TopicQueryFunction.ONLY_NEGATIVE_BUT_NEGATE)
|
||||
);
|
||||
|
||||
final Series elternElectricityEnergyPurchase = series("eltern/electricity/energy/purchase", "kWh", SeriesType.DELTA, 10);
|
||||
final Series elternElectricityPowerPurchase = series("eltern/electricity/power/purchase", "W", SeriesType.VARYING, 10);
|
||||
final Series elternElectricityEnergyDelivery = series("eltern/electricity/energy/delivery", "kWh", SeriesType.DELTA, 10);
|
||||
final Series elternElectricityPowerDelivery = series("eltern/electricity/power/delivery", "W", SeriesType.VARYING, 10);
|
||||
topicMeterNumber(
|
||||
"Eltern/SmartMeter/SENSOR",
|
||||
TimestampType.ISO_LOCAL_DATE_TIME,
|
||||
"$.Time",
|
||||
"sml_meter_number_raw:$.meter.number",
|
||||
new TopicQuery(elternElectricityEnergyPurchase, "$.meter.energy_purchased_kwh", 1),
|
||||
new TopicQuery(elternElectricityPowerPurchase, "$.meter.power_w", TopicQueryFunction.ONLY_POSITIVE),
|
||||
new TopicQuery(elternElectricityEnergyDelivery, "$.meter.energy_delivered_kwh", 1),
|
||||
new TopicQuery(elternElectricityPowerDelivery, "$.meter.power_w", TopicQueryFunction.ONLY_NEGATIVE_BUT_NEGATE)
|
||||
);
|
||||
|
||||
final Series gardenPressure = series("garden/pressure", "hPa", SeriesType.VARYING, 5);
|
||||
final Series gardenTemperature = series("garden/temperature", "°C", SeriesType.VARYING, 5);
|
||||
final Series gardenHumidityAbsolute = series("garden/humidity/absolute", "mg/L", SeriesType.VARYING, 5);
|
||||
@ -132,13 +153,17 @@ public class DemoService {
|
||||
|
||||
final Series cisternVolume = series("cistern/volume", "L", SeriesType.VARYING, 5);
|
||||
topic("cistern/volume/PatrixJson", "$.date", new TopicQuery(cisternVolume, "$.value"));
|
||||
|
||||
plotRepository.deleteAll();
|
||||
zuhauseEnergie();
|
||||
zuhauseTemperatur();
|
||||
eltern();
|
||||
}
|
||||
|
||||
private void plots() {
|
||||
plotRepository.deleteAll();
|
||||
|
||||
final Plot plot = plotRepository.save(new Plot());
|
||||
plot.setName("Test");
|
||||
private void zuhauseEnergie() {
|
||||
final Plot plot = plotRepository.save(new Plot(plotRepository.count()));
|
||||
plot.setName("Zuhause Energie");
|
||||
plot.setDashboard(true);
|
||||
|
||||
final Axis energy = axisRepository.save(new Axis(plot));
|
||||
plot.addAxis(energy);
|
||||
@ -148,13 +173,39 @@ public class DemoService {
|
||||
|
||||
final Series electricityEnergyPurchase = seriesRepository.findByName("electricity/energy/purchase").orElseThrow();
|
||||
final Graph electricityEnergyPurchaseGraph = graphRepository.save(new Graph(energy, electricityEnergyPurchase));
|
||||
electricityEnergyPurchaseGraph.setType(GraphType.BAR);
|
||||
electricityEnergyPurchaseGraph.setStack("a");
|
||||
electricityEnergyPurchaseGraph.setName("Bezug");
|
||||
electricityEnergyPurchaseGraph.setColor("#FF8800");
|
||||
energy.addGraph(electricityEnergyPurchaseGraph);
|
||||
|
||||
final Series electricityEnergyDelivery = seriesRepository.findByName("electricity/energy/delivery").orElseThrow();
|
||||
final Graph electricityEnergyDeliveryGraph = graphRepository.save(new Graph(energy, electricityEnergyDelivery));
|
||||
electricityEnergyDeliveryGraph.setType(GraphType.BAR);
|
||||
electricityEnergyDeliveryGraph.setStack("a");
|
||||
electricityEnergyDeliveryGraph.setName("Überschuss");
|
||||
electricityEnergyDeliveryGraph.setColor("#FF00FF");
|
||||
electricityEnergyDeliveryGraph.setFactor(-1);
|
||||
energy.addGraph(electricityEnergyDeliveryGraph);
|
||||
|
||||
final Series electricityEnergyProduce = seriesRepository.findByName("electricity/energy/produce").orElseThrow();
|
||||
final Graph electricityEnergyProduceGraph = graphRepository.save(new Graph(energy, electricityEnergyProduce));
|
||||
electricityEnergyProduceGraph.setType(GraphType.BAR);
|
||||
electricityEnergyProduceGraph.setStack("a");
|
||||
electricityEnergyProduceGraph.setName("Produktion");
|
||||
electricityEnergyProduceGraph.setColor("#0000FF");
|
||||
energy.addGraph(electricityEnergyProduceGraph);
|
||||
}
|
||||
|
||||
private void zuhauseTemperatur() {
|
||||
final Plot plot = plotRepository.save(new Plot(plotRepository.count()));
|
||||
plot.setName("Zuhause Temperaturen");
|
||||
plot.setDashboard(true);
|
||||
|
||||
final Axis temperature = axisRepository.save(new Axis(plot));
|
||||
plot.addAxis(temperature);
|
||||
temperature.setRight(true);
|
||||
temperature.setMin(0.0);
|
||||
temperature.setName("Temperatur");
|
||||
temperature.setUnit("°C");
|
||||
|
||||
@ -167,34 +218,51 @@ public class DemoService {
|
||||
bedroomTemperatureGraph.setMax(true);
|
||||
temperature.addGraph(bedroomTemperatureGraph);
|
||||
|
||||
final Axis status = axisRepository.save(new Axis(plot));
|
||||
plot.addAxis(status);
|
||||
status.setVisible(false);
|
||||
status.setRight(true);
|
||||
status.setName("Status");
|
||||
final Series gardenTemperature = seriesRepository.findByName("garden/temperature").orElseThrow();
|
||||
final Graph gardenTemperatureGraph = graphRepository.save(new Graph(temperature, gardenTemperature));
|
||||
gardenTemperatureGraph.setName("Garten");
|
||||
gardenTemperatureGraph.setColor("#00FF00");
|
||||
gardenTemperatureGraph.setMin(true);
|
||||
gardenTemperatureGraph.setAvg(false);
|
||||
gardenTemperatureGraph.setMax(true);
|
||||
temperature.addGraph(gardenTemperatureGraph);
|
||||
}
|
||||
|
||||
final Series fallbackRelay0 = seriesRepository.findByName("fallback/relay0").orElseThrow();
|
||||
final Graph fallbackRelay0Graph = graphRepository.save(new Graph(status, fallbackRelay0));
|
||||
fallbackRelay0Graph.setName("FallbackRelay0");
|
||||
fallbackRelay0Graph.setColor("#00FF00");
|
||||
status.addGraph(fallbackRelay0Graph);
|
||||
private void eltern() {
|
||||
final Plot plot = plotRepository.save(new Plot(plotRepository.count()));
|
||||
plot.setName("Eltern");
|
||||
|
||||
final Series infrared = seriesRepository.findByName("infraredHeater/state").orElseThrow();
|
||||
final Graph infraredGraph = graphRepository.save(new Graph(status, infrared));
|
||||
infraredGraph.setName("Infrarotheizung");
|
||||
infraredGraph.setColor("#FF00FF");
|
||||
status.addGraph(infraredGraph);
|
||||
final Axis energy = axisRepository.save(new Axis(plot));
|
||||
plot.addAxis(energy);
|
||||
plot.setDashboard(true);
|
||||
energy.setRight(true);
|
||||
energy.setName("Energie");
|
||||
energy.setUnit("kWh");
|
||||
|
||||
plotRepository.save(plot);
|
||||
final Series electricityEnergyPurchase = seriesRepository.findByName("eltern/electricity/energy/purchase").orElseThrow();
|
||||
final Graph electricityEnergyPurchaseGraph = graphRepository.save(new Graph(energy, electricityEnergyPurchase));
|
||||
electricityEnergyPurchaseGraph.setName("Bezug");
|
||||
electricityEnergyPurchaseGraph.setType(GraphType.BAR);
|
||||
electricityEnergyPurchaseGraph.setStack("a");
|
||||
electricityEnergyPurchaseGraph.setColor("#FF8800");
|
||||
energy.addGraph(electricityEnergyPurchaseGraph);
|
||||
|
||||
axisRepository.save(energy);
|
||||
axisRepository.save(temperature);
|
||||
axisRepository.save(status);
|
||||
final Series electricityEnergyDelivery = seriesRepository.findByName("eltern/electricity/energy/delivery").orElseThrow();
|
||||
final Graph electricityEnergyDeliveryGraph = graphRepository.save(new Graph(energy, electricityEnergyDelivery));
|
||||
electricityEnergyDeliveryGraph.setName("Überschuss");
|
||||
electricityEnergyDeliveryGraph.setType(GraphType.BAR);
|
||||
electricityEnergyDeliveryGraph.setFactor(-1);
|
||||
electricityEnergyDeliveryGraph.setStack("a");
|
||||
electricityEnergyDeliveryGraph.setColor("#FF00FF");
|
||||
energy.addGraph(electricityEnergyDeliveryGraph);
|
||||
|
||||
graphRepository.save(electricityEnergyPurchaseGraph);
|
||||
graphRepository.save(bedroomTemperatureGraph);
|
||||
graphRepository.save(fallbackRelay0Graph);
|
||||
graphRepository.save(infraredGraph);
|
||||
// final Series electricityEnergyProduce = seriesRepository.findByName("eltern/electricity/energy/produce").orElseThrow();
|
||||
// final Graph electricityEnergyProduceGraph = graphRepository.save(new Graph(energy, electricityEnergyProduce));
|
||||
// electricityEnergyProduceGraph.setName("Produktion");
|
||||
// electricityEnergyProduceGraph.setType(GraphType.BAR);
|
||||
// electricityEnergyProduceGraph.setStack("a");
|
||||
// electricityEnergyProduceGraph.setColor("#0000FF");
|
||||
// energy.addGraph(electricityEnergyProduceGraph);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -222,10 +290,11 @@ public class DemoService {
|
||||
topic.getQueries().addAll(List.of(queries));
|
||||
}
|
||||
|
||||
private void topicMeterNumber(@NonNull final String name, @NonNull final String meterNumberQuery, @NonNull final TopicQuery... queries) {
|
||||
private void topicMeterNumber(@NonNull final String name, @NonNull final TimestampType timestampType, @NonNull final String timestampQuery, @NonNull final String meterNumberQuery, @NonNull final TopicQuery... queries) {
|
||||
final Topic topic = topicRepository.findByName(name).orElseGet(() -> topicRepository.save(new Topic(name)));
|
||||
topic.setMeterNumberQuery(meterNumberQuery);
|
||||
topic.setTimestampQuery("$.timestamp");
|
||||
topic.setTimestampType(timestampType);
|
||||
topic.setTimestampQuery(timestampQuery);
|
||||
topic.getQueries().clear();
|
||||
topic.getQueries().addAll(List.of(queries));
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package de.ph87.data.topic;
|
||||
|
||||
public enum TimestampType {
|
||||
EPOCH_MILLISECONDS, EPOCH_SECONDS
|
||||
EPOCH_MILLISECONDS,
|
||||
EPOCH_SECONDS,
|
||||
ISO_LOCAL_DATE_TIME,
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Optional;
|
||||
@ -27,6 +28,8 @@ import java.util.Optional;
|
||||
@RequiredArgsConstructor
|
||||
public class TopicReceiver {
|
||||
|
||||
public static final String SML_METER_NUMBER_RAW = "sml_meter_number_raw:";
|
||||
|
||||
private final TopicRepository topicRepository;
|
||||
|
||||
private final BoolService boolService;
|
||||
@ -111,8 +114,7 @@ public class TopicReceiver {
|
||||
}
|
||||
|
||||
final Object valueRaw = json.read(query.getValueQuery());
|
||||
queryValue(valueRaw).ifPresentOrElse(
|
||||
v -> {
|
||||
queryValue(valueRaw).ifPresentOrElse(v -> {
|
||||
final double value = query.getFunction().apply(v) * query.getFactor();
|
||||
series.update(date, value);
|
||||
applicationEventPublisher.publishEvent(new SeriesDto(series, false));
|
||||
@ -129,9 +131,7 @@ public class TopicReceiver {
|
||||
}
|
||||
case VARYING -> varyingService.write(series, date, value);
|
||||
}
|
||||
},
|
||||
() -> topic.error(log, "Failed to parse value: %s".formatted(valueRaw))
|
||||
);
|
||||
}, () -> topic.error(log, "Failed to parse value: %s".formatted(valueRaw)));
|
||||
} catch (Exception e) {
|
||||
topic.error(log, "Error executing TopicQuery: %s\n topic=%s\n query=%s\n inbound=%s".formatted(e.toString(), topic, query, inbound), e);
|
||||
}
|
||||
@ -139,10 +139,28 @@ public class TopicReceiver {
|
||||
|
||||
@NonNull
|
||||
private static String queryMeterNumber(@NonNull final Topic topic, @NonNull final DocumentContext json) {
|
||||
if (topic.getMeterNumberQuery().startsWith("\"") && topic.getMeterNumberQuery().endsWith("\"")) {
|
||||
return topic.getMeterNumberQuery().substring(1, topic.getMeterNumberQuery().length() - 1);
|
||||
final String query = topic.getMeterNumberQuery();
|
||||
if (query.startsWith(SML_METER_NUMBER_RAW)) {
|
||||
final String field = query.substring(SML_METER_NUMBER_RAW.length());
|
||||
final String raw = json.read(field, String.class);
|
||||
if (raw.isEmpty()) {
|
||||
throw new NumberFormatException("Cannot parse Meter number: No Hex-chars read.");
|
||||
}
|
||||
return json.read(topic.getMeterNumberQuery(), String.class);
|
||||
if (raw.length() % 2 != 0) {
|
||||
throw new NumberFormatException("Cannot parse Meter number: Hex-char count must be multiple of 2.");
|
||||
}
|
||||
final int length = Integer.parseInt(raw.substring(0, 2), 16);
|
||||
if (raw.length() != length * 2) {
|
||||
throw new NumberFormatException("Cannot parse Meter number: Invalid length");
|
||||
}
|
||||
final int type = Integer.parseInt(raw.substring(2, 4), 16);
|
||||
final String name = "" + (char) Integer.parseInt(raw.substring(4, 6), 16) + (char) Integer.parseInt(raw.substring(6, 8), 16) + (char) Integer.parseInt(raw.substring(8, 10), 16);
|
||||
final int number = Integer.parseInt(raw.substring(10), 16);
|
||||
return "%d%s%s".formatted(type, name, number);
|
||||
} else if (query.startsWith("\"") && query.endsWith("\"")) {
|
||||
return query.substring(1, query.length() - 1);
|
||||
}
|
||||
return json.read(query, String.class);
|
||||
}
|
||||
|
||||
private static boolean queryBoolean(@NonNull final DocumentContext json, @NonNull final String terminatedQuery) {
|
||||
@ -181,6 +199,7 @@ public class TopicReceiver {
|
||||
return switch (type) {
|
||||
case TimestampType.EPOCH_SECONDS -> ZonedDateTime.ofInstant(Instant.ofEpochSecond(json.read(query, Long.class)), ZoneId.systemDefault());
|
||||
case TimestampType.EPOCH_MILLISECONDS -> ZonedDateTime.ofInstant(Instant.ofEpochMilli(json.read(query, Long.class)), ZoneId.systemDefault());
|
||||
case TimestampType.ISO_LOCAL_DATE_TIME -> ZonedDateTime.of(LocalDateTime.parse(json.read(query, String.class)), ZoneId.systemDefault());
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user